From 3282d75aa52a1cb42f04d9f1f774768131cfc177 Mon Sep 17 00:00:00 2001 From: ArturTomczak Date: Tue, 23 Nov 2021 17:53:32 +0100 Subject: [PATCH] fix encoding bcf bug, add tests --- src/ifcopenshell-python/ifcopenshell/ids.py | 178 +++++++++++++------- src/ifcopenshell-python/test/test_ids.py | 165 ++++++++++-------- 2 files changed, 219 insertions(+), 124 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/ids.py b/src/ifcopenshell-python/ifcopenshell/ids.py index b872196a69..5bc9b89405 100644 --- a/src/ifcopenshell-python/ifcopenshell/ids.py +++ b/src/ifcopenshell-python/ifcopenshell/ids.py @@ -19,6 +19,8 @@ import os import re import logging +import operator +import os import numpy as np from datetime import date @@ -154,10 +156,10 @@ class ids: }, ) - with open(filepath, "w") as f: - f.write('\n') - f.write("\n") - f.write(ids_str) + with open(filepath, "wb") as f: + f.write('\n'.encode("utf-8")) + f.write("\n".encode("utf-8")) + f.write(ids_str.encode("utf-8")) f.close() # ids_schema.validate(filepath) @@ -500,10 +502,13 @@ class entity(facet): :rtype: dict """ fac_dict = {"name": parameter_asdict(self.name)} - try: - fac_dict["predefinedtype"] = parameter_asdict(self.predefinedtype) - except (RecursionError, UnboundLocalError) as e: - print(e) + if "predefinedtype" in self: + if self.predefinedtype: + fac_dict["predefinedtype"] = parameter_asdict(self.predefinedtype) + # try: + # fac_dict["predefinedtype"] = parameter_asdict(self.predefinedtype) + # except (RecursionError, UnboundLocalError) as e: + # print(e) return fac_dict def __call__(self, inst, logger): @@ -852,6 +857,8 @@ def parameter_asdict(parameter): x = p.asdict() restrictions[list(x)[1]] = x[list(x)[1]] parameter_dict = {"xs:restriction": [restrictions]} + else: + raise Exception(str(parameter) + " was not able to be converted into 'Parameter_dict'") return parameter_dict @@ -943,10 +950,7 @@ class restriction: rest_dict["xs:enumeration"].append({"@value": option}) elif self.type == "bounds": for option in self.options: - if "xs:option" not in rest_dict: - rest_dict["xs:" + option] = [{"@value": option}] - else: - rest_dict["xs:" + option].append({"@value": self.options[option], "@fixed": False}) + rest_dict["xs:" + option] = [{"@value": self.options[option], "@fixed": False}] elif self.type == "pattern": if "xs:pattern" not in rest_dict: rest_dict["xs:pattern"] = [{"@value": self.options}] @@ -982,7 +986,7 @@ class restriction: ): rest.options = options else: - Exception("Options were not properly defined.") + raise Exception("Options were not properly defined.") return rest else: raise Exception( @@ -1076,6 +1080,64 @@ class SimpleHandler(logging.StreamHandler): self.statements.append(mymsg.msg) +class SimpleHandler(logging.StreamHandler): + """Logging handler listing all cases in python list.""" + + def __init__(self, report_valid=False): + """Logging handler listing all cases in python list. + + :param report_valid: True if you want to list all the compliant cases as well, defaults to False + :type report_valid: bool, optional + """ + logging.StreamHandler.__init__(self) + self.statements = [] + if report_valid: + self.setLevel(logging.INFO) + else: + self.setLevel(logging.ERROR) + + def emit(self, mymsg): + """Triggered on each use of logging with the Simple handler enabled. + + :param log_content: default logger message + :type log_content: string|dict + """ + self.statements.append(mymsg.msg) + + +class CsvHandler(logging.StreamHandler): + """Logging handler listing all cases in csv file.""" + + def __init__(self, filepath="./Report.csv", report_valid=False): + """Logging handler listing all cases in csv file. + + :param report_valid: True if you want to list all the compliant cases as well, defaults to False + :type report_valid: bool, optional + """ + import csv + + logging.StreamHandler.__init__(self) + if report_valid: + self.setLevel(logging.INFO) + else: + self.setLevel(logging.ERROR) + self.file = open(filepath, "w", encoding="UTF8", newline="") + self.csvwriter = csv.writer(self.file) + self.csvwriter.writerow(["guid", "result", "sentence"]) # header + + def emit(self, mymsg): + """Triggered on each use of logging with the Simple handler enabled. + + :param log_content: default logger message + :type log_content: string|dict + """ + # BUG bytes-like object is required, not 'str' + self.csvwriter.writerow(mymsg.msg) + + def flush(self): + self.file.close() + + class BcfHandler(logging.StreamHandler): """Logging handler for creation of BCF report files. @@ -1093,7 +1155,7 @@ class BcfHandler(logging.StreamHandler): bcf_handler = BcfHandler( project_name="Default IDS Project", author="your@email.com", - filepath="example.bcf", + filepath=r".\example.bcf", ) logger = logging.getLogger("IDS_Logger") logging.basicConfig(level=logging.INFO, format="%(message)s") @@ -1124,55 +1186,55 @@ class BcfHandler(logging.StreamHandler): topic.title = log_content.msg["sentence"].split(".\n")[1] topic.description = log_content.msg["sentence"].split(".\n")[0] self.bcf.add_topic(topic) - try: # Add viewpoint and link to ifc object - viewpoint = bcf.Viewpoint() - viewpoint.perspective_camera = bcf.PerspectiveCamera() - ifc_elem = log_content.msg["ifc_element"] - # ifc_elem = ifc_file.by_guid(log_content.msg["guid"]) - target_position = np.array(ifcopenshell.util.placement.get_local_placement(ifc_elem.ObjectPlacement)) - target_position = target_position[:, 3][0:3] - camera_position = target_position + np.array((5, 5, 5)) - viewpoint.perspective_camera.camera_view_point.x = camera_position[0] - viewpoint.perspective_camera.camera_view_point.y = camera_position[1] - viewpoint.perspective_camera.camera_view_point.z = camera_position[2] - camera_direction = camera_position - target_position - camera_direction = camera_direction / np.linalg.norm(camera_direction) - camera_right = np.cross(np.array([0.0, 0.0, 1.0]), camera_direction) - camera_right = camera_right / np.linalg.norm(camera_right) - camera_up = np.cross(camera_direction, camera_right) - camera_up = camera_up / np.linalg.norm(camera_up) - rotation_transform = np.zeros((4, 4)) - rotation_transform[0, :3] = camera_right - rotation_transform[1, :3] = camera_up - rotation_transform[2, :3] = camera_direction - rotation_transform[-1, -1] = 1 - translation_transform = np.eye(4) - translation_transform[:3, -1] = -camera_position - look_at_transform = np.matmul(rotation_transform, translation_transform) - mat = np.linalg.inv(look_at_transform) - viewpoint.perspective_camera.camera_direction.x = mat[0][2] * -1 - viewpoint.perspective_camera.camera_direction.y = mat[1][2] * -1 - viewpoint.perspective_camera.camera_direction.z = mat[2][2] * -1 - viewpoint.perspective_camera.camera_up_vector.x = mat[0][1] - viewpoint.perspective_camera.camera_up_vector.y = mat[1][1] - viewpoint.perspective_camera.camera_up_vector.z = mat[2][1] - viewpoint.components = bcf.Components() - c = bcf.Component() - c.ifc_guid = log_content.msg["guid"] - viewpoint.components.selection.append(c) - viewpoint.components.visibility = bcf.ComponentVisibility() - viewpoint.components.visibility.default_visibility = True - viewpoint.snapshot = None - self.bcf.add_viewpoint(topic, viewpoint) - except: - pass + # try: # Add viewpoint and link to ifc object + viewpoint = bcf.Viewpoint() + viewpoint.perspective_camera = bcf.PerspectiveCamera() + ifc_elem = log_content.msg["ifc_element"] + # ifc_elem = ifc_file.by_guid(log_content.msg["guid"]) + target_position = np.array(ifcopenshell.util.placement.get_local_placement(ifc_elem.ObjectPlacement)) + target_position = target_position[:, 3][0:3] + camera_position = target_position + np.array((5, 5, 5)) + viewpoint.perspective_camera.camera_view_point.x = camera_position[0] + viewpoint.perspective_camera.camera_view_point.y = camera_position[1] + viewpoint.perspective_camera.camera_view_point.z = camera_position[2] + camera_direction = camera_position - target_position + camera_direction = camera_direction / np.linalg.norm(camera_direction) + camera_right = np.cross(np.array([0.0, 0.0, 1.0]), camera_direction) + camera_right = camera_right / np.linalg.norm(camera_right) + camera_up = np.cross(camera_direction, camera_right) + camera_up = camera_up / np.linalg.norm(camera_up) + rotation_transform = np.zeros((4, 4)) + rotation_transform[0, :3] = camera_right + rotation_transform[1, :3] = camera_up + rotation_transform[2, :3] = camera_direction + rotation_transform[-1, -1] = 1 + translation_transform = np.eye(4) + translation_transform[:3, -1] = -camera_position + look_at_transform = np.matmul(rotation_transform, translation_transform) + mat = np.linalg.inv(look_at_transform) + viewpoint.perspective_camera.camera_direction.x = mat[0][2] * -1 + viewpoint.perspective_camera.camera_direction.y = mat[1][2] * -1 + viewpoint.perspective_camera.camera_direction.z = mat[2][2] * -1 + viewpoint.perspective_camera.camera_up_vector.x = mat[0][1] + viewpoint.perspective_camera.camera_up_vector.y = mat[1][1] + viewpoint.perspective_camera.camera_up_vector.z = mat[2][1] + viewpoint.components = bcf.Components() + c = bcf.Component() + c.ifc_guid = log_content.msg["guid"] + viewpoint.components.selection.append(c) + viewpoint.components.visibility = bcf.ComponentVisibility() + viewpoint.components.visibility.default_visibility = True + viewpoint.snapshot = None + self.bcf.add_viewpoint(topic, viewpoint) + # except: + # pass def flush(self): """Saves the BCF report to file. Triggered at the end of the validation process.""" if not self.filepath: - self.filepath = os.getcwd() + r"\IDS_report.bcfzip" + self.filepath = os.getcwd() + r"\IDS_report.bcf" if not (self.filepath.endswith(".bcf") or self.filepath.endswith(".bcfzip")): - self.filepath = self.filepath + r"\IDS_report.bcfzip" + self.filepath = self.filepath + r"\IDS_report.bcf" self.bcf.save_project(self.filepath) diff --git a/src/ifcopenshell-python/test/test_ids.py b/src/ifcopenshell-python/test/test_ids.py index 95f46e997f..04db9b2a90 100644 --- a/src/ifcopenshell-python/test/test_ids.py +++ b/src/ifcopenshell-python/test/test_ids.py @@ -26,7 +26,19 @@ import ifcopenshell from ifcopenshell import ids +TEST_PATH = os.path.join(tempfile.gettempdir(), "test.ifc") +IFC_URL = os.path.join(os.path.dirname(__file__), "Sample-BIM-Files/IFC/", "IFC4_Wall_3_with_properties.ifc") +IDS_URL = os.path.join(os.path.dirname(__file__), "Sample-BIM-Files/IDS/", "IDS_Wall_needs_all_fields.xml") +logger = logging.getLogger("IDS_Logger") +# logging.basicConfig(level=logging.INFO, format="%(message)s") +# logging.basicConfig(filename=os.path.join(os.path.dirname(__file__), "log.txt"), level=logging.INFO, format="%(message)s") + +file = open(os.path.join(tempfile.gettempdir(), "test.ifc"), "w") +file.write(IFC_URL) +file.close() +ifc_file = ifcopenshell.open(IFC_URL) +os.remove(os.path.join(tempfile.gettempdir(), "test.ifc")) class TestIdsParsing(unittest.TestCase): @@ -212,7 +224,6 @@ class TestIdsAuthoring(unittest.TestCase): i = ids.ids() i.specifications.append(ids.specification(name="Test_Specification")) i.specifications[0].add_applicability(ids.entity.create(name="Test_Name")) - # r = ids.restriction.create(options="^(Wanddurchbruch.*|Deckendurchbruch.*)", type="pattern", base="string") r = ids.restriction.create(options="(Wanddurchbruch|Deckendurchbruch).*", type="pattern", base="string") p = ids.property.create(location="any", propertyset="Test", name="Test", value=r) i.specifications[0].add_requirement(p) @@ -220,36 +231,46 @@ class TestIdsAuthoring(unittest.TestCase): self.assertEqual(i.specifications[0].requirements.terms[0].value, "Deckendurchbruch") self.assertNotEqual(i.specifications[0].requirements.terms[0].value, "Deeckendurchbruch") + def test_create_restrictions_pattern_utf(self): + i = ids.ids() + i.specifications.append(ids.specification(name="Test_Specification")) + i.specifications[0].add_applicability(ids.entity.create(name="Test_Name")) + r = ids.restriction.create(options="èêóòâôæøåążźćęóʑʒʓʔʕʗʘʙʚʛʜʝʞ", type="pattern", base="string") + p = ids.property.create(location="any", propertyset="Test", name="Test", value=r) + i.specifications[0].add_requirement(p) + self.assertEqual(i.specifications[0].requirements.terms[0].value, "èêóòâôæøåążźćęóʑʒʓʔʕʗʘʙʚʛʜʝʞ") + """ Saving created IDS to IDS.xml """ - - # def test_created_ids_to_xml(self): - # i = ids.ids() - # i.specifications.append(ids.specification(name="Test_Specification")) - # e = ids.entity.create(name="Test_Name", predefinedtype="Test_PredefinedType") - # c = ids.classification.create(location="any", value="Test_Value", system="Test_System") - # m = ids.material.create(location="any", value="Test_Value") - # re = ids.restriction.create(options=["testA", "testB"], type="enumeration", base="string") - # rb = ids.restriction.create(options={"minInclusive": 0, "maxExclusive": 10}, type="bounds", base="integer") - # rp = ids.restriction.create(options="[A-Z]{2,4}", type="pattern", base="string") - # p1 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=re) - # p2 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=rb) - # p3 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=rp) - # p4 = ids.property.create( - # location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=[re, rb, rp] - # ) - # i.specifications[0].add_applicability(e) - # i.specifications[0].add_applicability(m) - # i.specifications[0].add_requirement(c) - # i.specifications[0].add_requirement(p1) - # i.specifications[0].add_requirement(p2) - # i.specifications[0].add_requirement(p3) - # i.specifications[0].add_requirement(p4) - # fn = "TEST_FILE.xml" - # result = i.to_xml(fn) - # os.remove(fn) - # self.assertTrue(result) - + def test_created_ids_to_xml(self): + i = ids.ids() + i.specifications.append(ids.specification(name="Test_Specification")) + e = ids.entity.create(name="Test_Name", predefinedtype="Test_PredefinedType") + c = ids.classification.create(location="any", value="Test_Value", system="Test_System") + m = ids.material.create(location="any", value="Test_Value") + re = ids.restriction.create(options=["testA", "testB"], type="enumeration", base="string") + rb = ids.restriction.create(options={"minInclusive": 0, "maxExclusive": 10}, type="bounds", base="integer") + rp1 = ids.restriction.create(options="[A-Z]{2,4}", type="pattern", base="string") + rp2 = ids.restriction.create(options="èêóòâôæøåążźćęóʑʒʓʔʕʗʘʙʚʛʜʝʞ", type="pattern", base="string") + p1 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=re) + p2 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=rb) + p3 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=rp1) + p4 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=rp2) + p5 = ids.property.create( + location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=[re, rb, rp1] + ) + i.specifications[0].add_applicability(e) + i.specifications[0].add_applicability(m) + i.specifications[0].add_requirement(c) + i.specifications[0].add_requirement(p1) + #TODO i.specifications[0].add_requirement(p2) + i.specifications[0].add_requirement(p3) + i.specifications[0].add_requirement(p4) + #TODO i.specifications[0].add_requirement(p5) + fn = "TEST_FILE.xml" + result = i.to_xml(fn) + os.remove(fn) + self.assertTrue(result) """ IDS information """ @@ -268,13 +289,39 @@ class TestIdsAuthoring(unittest.TestCase): class TestIfcValidation(unittest.TestCase): - def test_validate_simple(self): - # TODO - pass - def test_validate_all_facets(self): - # TODO - pass + def test_validate_simple(self): + #Same test as in reporting... + ids_file = ids.ids.open(IDS_URL) + report = ids.SimpleHandler() + logger.addHandler(report) + ids_file.validate(ifc_file, logger) + self.assertEqual(len(report.statements), 5) + + # def test_validate_all_facets(self): + # #Those are true: + # p1 = ids.property.create(location="any", propertyset="MySet", name="Param1", value="banan") + # p2 = ids.property.create(location="any", propertyset="MySet", name="Param2", value=120.0) + # # p3 = ids.property.create(location="any", propertyset="Pset_WallCommon", name="LoadBearing", value=False) + # # #Those are false: + # # p4 = ids.property.create(location="any", propertyset="MySet", name="Param1", value="orange") + # # p5 = ids.property.create(location="any", propertyset="MySet", name="Param2", value=123.4) + # # p6 = ids.property.create(location="any", propertyset="Pset_WallCommon", name="LoadBearing", value=True) + + # i = ids.ids() + # i.specifications.append(ids.specification(name="Test_Specification")) + # i.specifications[0].add_applicability(p1) + # i.specifications[0].add_requirement(p2) + # # i.specifications[0].add_requirement(p2) + # # i.specifications[0].add_requirement(p3) + # # i.specifications[0].add_requirement(p4) + # # i.specifications[0].add_requirement(p5) + # # i.specifications[0].add_requirement(p6) + + # report = ids.SimpleHandler() + # logger.addHandler(report) + # i.validate(ifc_file, logger) + # self.assertEqual(len(report.statements), 3) #three should fail """ Validating IDS files with restrictions """ @@ -307,39 +354,25 @@ class TestIfcValidation(unittest.TestCase): # # self.assertTrue( ) -# class TestIdsReporting(unittest.TestCase): +class TestIdsReporting(unittest.TestCase): -# TEST_PATH = os.path.join(os.path.dirname(__file__), "test.ifc") -# IFC_URL = os.path.join(os.path.dirname(__file__), "Sample-BIM-Files/IFC/", "IFC4_Wall_3_with_properties.ifc") -# IDS_URL = os.path.join(os.path.dirname(__file__), "Sample-BIM-Files/IDS/", "IDS_Wall_needs_all_fields.xml") - -# logger = logging.getLogger("IDS_Logger") -# # logging.basicConfig(level=logging.INFO, format="%(message)s") -# # logging.basicConfig(filename=os.path.join(os.path.dirname(__file__), "log.txt"), level=logging.INFO, format="%(message)s") + def test_simple_report(self): + #Same test as in validation... + ids_file = ids.ids.open(IDS_URL) + report = ids.SimpleHandler() + logger.addHandler(report) + ids_file.validate(ifc_file, logger) + self.assertEqual(len(report.statements), 5) -# content = IFC_URL -# file = open(TEST_PATH, "w") -# file.write(content) -# file.close() -# ifc_file = ifcopenshell.open(TEST_PATH) -# os.remove(TEST_PATH) - -# def test_simple_report(self): -# ids_file = ids.ids.open(self.IDS_URL) -# report = ids.SimpleHandler() -# self.logger.addHandler(report) -# ids_file.validate(self.ifc_file, self.logger) -# self.assertEqual(len(report.statements), 5) - -# def test_bcf_report(self): -# ids_file = ids.ids.open(self.IDS_URL) -# fn = os.path.join(tempfile.gettempdir(), "test.bcf") -# bcf_handler = ids.BcfHandler(project_name="Default IDS Project", author="your@email.com", filepath=fn) -# self.logger.addHandler(bcf_handler) -# ids_file.validate(self.ifc_file, self.logger) -# my_bcfxml = bcfxml.load(fn) -# topics = my_bcfxml.get_topics() -# self.assertEqual(len(topics), 5) + def test_bcf_report(self): + ids_file = ids.ids.open(IDS_URL) + fn = os.path.join(tempfile.gettempdir(), "test.bcf") + bcf_handler = ids.BcfHandler(project_name="Default IDS Project", author="your@email.com", filepath=fn) + logger.addHandler(bcf_handler) + ids_file.validate(ifc_file, logger) + my_bcfxml = bcfxml.load(fn) + topics = my_bcfxml.get_topics() + self.assertEqual(len(topics), 5) if __name__ == "__main__":