From 4cf6411a61b622225effd9d85cbe5bf8221ed597 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Thu, 17 Apr 2025 12:05:14 +0500 Subject: [PATCH] ifc5d.csv2ifc - add tests for sample files --- src/ifc5d/Makefile | 4 ++ src/ifc5d/test/__init__.py | 0 src/ifc5d/test/test_csv2ifc.py | 119 +++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 src/ifc5d/test/__init__.py create mode 100644 src/ifc5d/test/test_csv2ifc.py diff --git a/src/ifc5d/Makefile b/src/ifc5d/Makefile index e6bf8b36dc..f3d35c2d29 100644 --- a/src/ifc5d/Makefile +++ b/src/ifc5d/Makefile @@ -19,6 +19,10 @@ PACKAGE_NAME:=ifc5d include ../common.mk +.PHONY: test +test: + pytest -p no:pytest-blender test + .PHONY: qa qa: black . diff --git a/src/ifc5d/test/__init__.py b/src/ifc5d/test/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/ifc5d/test/test_csv2ifc.py b/src/ifc5d/test/test_csv2ifc.py new file mode 100644 index 0000000000..49858ba386 --- /dev/null +++ b/src/ifc5d/test/test_csv2ifc.py @@ -0,0 +1,119 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2022 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import tempfile +import csv +import pytest +import ifcopenshell +import ifcopenshell.api.root +import ifcopenshell.api.unit +import ifcopenshell.util.cost +import ifcopenshell.util.element +import ifc5d.csv2ifc +import ifc5d.ifc5Dspreadsheet +from pathlib import Path + + +class TestCsv2Ifc: + @staticmethod + def setup_ifc_file() -> ifcopenshell.file: + ifc_file = ifcopenshell.file() + ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcProject") + unit = ifcopenshell.api.unit.add_si_unit(ifc_file, unit_type="LENGTHUNIT", prefix="MILLI") + ifcopenshell.api.unit.assign_unit(ifc_file, units=[unit]) + return ifc_file + + @staticmethod + def get_items_rows_from_csv(csv_filepath: Path) -> list[list[str]]: + with csv_filepath.open("r", encoding="utf-8") as csv_file: + reader = csv.reader(csv_file) + rows = list(reader) + rows = rows[1:] # Skip header. + rows = filter(lambda l: l[0], rows) # Skip empty rows. + rows = list(rows) + return rows + + @staticmethod + def validate_ifc_file_against_csv(ifc_file: ifcopenshell.file, csv_filepath: Path) -> None: + assert len(cost_schedules := ifc_file.by_type("IfcCostSchedule")) == 1 + all_cost_items = ifc_file.by_type("IfcCostItem") + + # Ensure everything was imported. + rows = TestCsv2Ifc.get_items_rows_from_csv(csv_filepath) + assert len(rows) == len(all_cost_items) + + # A rought test that some hierarchy was established. + root_items = ifcopenshell.util.cost.get_root_cost_items(cost_schedules[0]) + assert len(all_cost_items) > len(root_items) + all_nested_items = [i for item in root_items for i in ifcopenshell.util.cost.get_all_nested_cost_items(item)] + assert len(all_nested_items + root_items) == len(all_cost_items) + + @pytest.mark.parametrize("csv_filepath", Path(__file__).parent.parent.glob("*.csv")) + def test_import_sample_files(self, csv_filepath: Path): + ifc_file = self.setup_ifc_file() + ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcProject") + unit = ifcopenshell.api.unit.add_si_unit(ifc_file, unit_type="LENGTHUNIT", prefix="MILLI") + ifcopenshell.api.unit.assign_unit(ifc_file, units=[unit]) + + csv2ifc = ifc5d.csv2ifc.Csv2Ifc(str(csv_filepath), ifc_file) + csv2ifc.execute() + + self.validate_ifc_file_against_csv(ifc_file, csv_filepath) + + def test_export_import_test(self): + ifc_file = self.setup_ifc_file() + + csv_filepath = Path(__file__).parent.parent / "sample_cost_schedule_house_FR.csv" + csv2ifc = ifc5d.csv2ifc.Csv2Ifc(str(csv_filepath), ifc_file) + csv2ifc.execute() + + self.validate_ifc_file_against_csv(ifc_file, csv_filepath) + n_rows = len(self.get_items_rows_from_csv(csv_filepath)) + + # Export it and import it to different IFC file.. + with tempfile.TemporaryDirectory("w") as temp_csv_dir: + writer = ifc5d.ifc5Dspreadsheet.Ifc5DCsvWriter(ifc_file, temp_csv_dir) + writer.write() + + new_csv_filepath = next(Path(temp_csv_dir).glob("*.csv")) + n_rows_ = len(self.get_items_rows_from_csv(new_csv_filepath)) + assert n_rows == n_rows_ + + new_ifc_file = self.setup_ifc_file() + csv2ifc = ifc5d.csv2ifc.Csv2Ifc(str(new_csv_filepath), new_ifc_file) + csv2ifc.execute() + + self.validate_ifc_file_against_csv(new_ifc_file, new_csv_filepath) + + def test_export_xlsx_ods(self): + ifc_file = self.setup_ifc_file() + + csv_filepath = Path(__file__).parent.parent / "sample_cost_schedule_house_FR.csv" + csv2ifc = ifc5d.csv2ifc.Csv2Ifc(str(csv_filepath), ifc_file) + csv2ifc.execute() + + self.validate_ifc_file_against_csv(ifc_file, csv_filepath) + + # Just to make sure it works. + with tempfile.TemporaryDirectory("w") as temp_csv_dir: + writer = ifc5d.ifc5Dspreadsheet.Ifc5DOdsWriter(ifc_file, temp_csv_dir) + writer.write() + writer = ifc5d.ifc5Dspreadsheet.Ifc5DXlsxWriter(ifc_file, temp_csv_dir) + writer.write() + assert len(list(Path(temp_csv_dir).glob("*.ods"))) == 1 + assert len(list(Path(temp_csv_dir).glob("*.xlsx"))) == 1