fix encoding bcf bug, add tests

This commit is contained in:
ArturTomczak
2021-11-23 17:53:32 +01:00
committed by Thomas Krijnen
parent 72fc617196
commit 3282d75aa5
2 changed files with 219 additions and 124 deletions
+120 -58
View File
@@ -19,6 +19,8 @@
import os import os
import re import re
import logging import logging
import operator
import os
import numpy as np import numpy as np
from datetime import date from datetime import date
@@ -154,10 +156,10 @@ class ids:
}, },
) )
with open(filepath, "w") as f: with open(filepath, "wb") as f:
f.write('<?xml version="1.0" encoding="UTF-8"?>\n') f.write('<?xml version="1.0" encoding="UTF-8"?>\n'.encode("utf-8"))
f.write("<!-- IDS (INFORMATION DELIVERY SPECIFICATION) CREATED USING IFCOPENSHELL -->\n") f.write("<!-- IDS (INFORMATION DELIVERY SPECIFICATION) CREATED USING IFCOPENSHELL -->\n".encode("utf-8"))
f.write(ids_str) f.write(ids_str.encode("utf-8"))
f.close() f.close()
# ids_schema.validate(filepath) # ids_schema.validate(filepath)
@@ -500,10 +502,13 @@ class entity(facet):
:rtype: dict :rtype: dict
""" """
fac_dict = {"name": parameter_asdict(self.name)} fac_dict = {"name": parameter_asdict(self.name)}
try: if "predefinedtype" in self:
fac_dict["predefinedtype"] = parameter_asdict(self.predefinedtype) if self.predefinedtype:
except (RecursionError, UnboundLocalError) as e: fac_dict["predefinedtype"] = parameter_asdict(self.predefinedtype)
print(e) # try:
# fac_dict["predefinedtype"] = parameter_asdict(self.predefinedtype)
# except (RecursionError, UnboundLocalError) as e:
# print(e)
return fac_dict return fac_dict
def __call__(self, inst, logger): def __call__(self, inst, logger):
@@ -852,6 +857,8 @@ def parameter_asdict(parameter):
x = p.asdict() x = p.asdict()
restrictions[list(x)[1]] = x[list(x)[1]] restrictions[list(x)[1]] = x[list(x)[1]]
parameter_dict = {"xs:restriction": [restrictions]} parameter_dict = {"xs:restriction": [restrictions]}
else:
raise Exception(str(parameter) + " was not able to be converted into 'Parameter_dict'")
return parameter_dict return parameter_dict
@@ -943,10 +950,7 @@ class restriction:
rest_dict["xs:enumeration"].append({"@value": option}) rest_dict["xs:enumeration"].append({"@value": option})
elif self.type == "bounds": elif self.type == "bounds":
for option in self.options: for option in self.options:
if "xs:option" not in rest_dict: rest_dict["xs:" + option] = [{"@value": self.options[option], "@fixed": False}]
rest_dict["xs:" + option] = [{"@value": option}]
else:
rest_dict["xs:" + option].append({"@value": self.options[option], "@fixed": False})
elif self.type == "pattern": elif self.type == "pattern":
if "xs:pattern" not in rest_dict: if "xs:pattern" not in rest_dict:
rest_dict["xs:pattern"] = [{"@value": self.options}] rest_dict["xs:pattern"] = [{"@value": self.options}]
@@ -982,7 +986,7 @@ class restriction:
): ):
rest.options = options rest.options = options
else: else:
Exception("Options were not properly defined.") raise Exception("Options were not properly defined.")
return rest return rest
else: else:
raise Exception( raise Exception(
@@ -1076,6 +1080,64 @@ class SimpleHandler(logging.StreamHandler):
self.statements.append(mymsg.msg) 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): class BcfHandler(logging.StreamHandler):
"""Logging handler for creation of BCF report files. """Logging handler for creation of BCF report files.
@@ -1093,7 +1155,7 @@ class BcfHandler(logging.StreamHandler):
bcf_handler = BcfHandler( bcf_handler = BcfHandler(
project_name="Default IDS Project", project_name="Default IDS Project",
author="your@email.com", author="your@email.com",
filepath="example.bcf", filepath=r".\example.bcf",
) )
logger = logging.getLogger("IDS_Logger") logger = logging.getLogger("IDS_Logger")
logging.basicConfig(level=logging.INFO, format="%(message)s") 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.title = log_content.msg["sentence"].split(".\n")[1]
topic.description = log_content.msg["sentence"].split(".\n")[0] topic.description = log_content.msg["sentence"].split(".\n")[0]
self.bcf.add_topic(topic) self.bcf.add_topic(topic)
try: # Add viewpoint and link to ifc object # try: # Add viewpoint and link to ifc object
viewpoint = bcf.Viewpoint() viewpoint = bcf.Viewpoint()
viewpoint.perspective_camera = bcf.PerspectiveCamera() viewpoint.perspective_camera = bcf.PerspectiveCamera()
ifc_elem = log_content.msg["ifc_element"] ifc_elem = log_content.msg["ifc_element"]
# ifc_elem = ifc_file.by_guid(log_content.msg["guid"]) # 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 = np.array(ifcopenshell.util.placement.get_local_placement(ifc_elem.ObjectPlacement))
target_position = target_position[:, 3][0:3] target_position = target_position[:, 3][0:3]
camera_position = target_position + np.array((5, 5, 5)) 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.x = camera_position[0]
viewpoint.perspective_camera.camera_view_point.y = camera_position[1] viewpoint.perspective_camera.camera_view_point.y = camera_position[1]
viewpoint.perspective_camera.camera_view_point.z = camera_position[2] viewpoint.perspective_camera.camera_view_point.z = camera_position[2]
camera_direction = camera_position - target_position camera_direction = camera_position - target_position
camera_direction = camera_direction / np.linalg.norm(camera_direction) 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 = np.cross(np.array([0.0, 0.0, 1.0]), camera_direction)
camera_right = camera_right / np.linalg.norm(camera_right) camera_right = camera_right / np.linalg.norm(camera_right)
camera_up = np.cross(camera_direction, camera_right) camera_up = np.cross(camera_direction, camera_right)
camera_up = camera_up / np.linalg.norm(camera_up) camera_up = camera_up / np.linalg.norm(camera_up)
rotation_transform = np.zeros((4, 4)) rotation_transform = np.zeros((4, 4))
rotation_transform[0, :3] = camera_right rotation_transform[0, :3] = camera_right
rotation_transform[1, :3] = camera_up rotation_transform[1, :3] = camera_up
rotation_transform[2, :3] = camera_direction rotation_transform[2, :3] = camera_direction
rotation_transform[-1, -1] = 1 rotation_transform[-1, -1] = 1
translation_transform = np.eye(4) translation_transform = np.eye(4)
translation_transform[:3, -1] = -camera_position translation_transform[:3, -1] = -camera_position
look_at_transform = np.matmul(rotation_transform, translation_transform) look_at_transform = np.matmul(rotation_transform, translation_transform)
mat = np.linalg.inv(look_at_transform) mat = np.linalg.inv(look_at_transform)
viewpoint.perspective_camera.camera_direction.x = mat[0][2] * -1 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.y = mat[1][2] * -1
viewpoint.perspective_camera.camera_direction.z = mat[2][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.x = mat[0][1]
viewpoint.perspective_camera.camera_up_vector.y = mat[1][1] viewpoint.perspective_camera.camera_up_vector.y = mat[1][1]
viewpoint.perspective_camera.camera_up_vector.z = mat[2][1] viewpoint.perspective_camera.camera_up_vector.z = mat[2][1]
viewpoint.components = bcf.Components() viewpoint.components = bcf.Components()
c = bcf.Component() c = bcf.Component()
c.ifc_guid = log_content.msg["guid"] c.ifc_guid = log_content.msg["guid"]
viewpoint.components.selection.append(c) viewpoint.components.selection.append(c)
viewpoint.components.visibility = bcf.ComponentVisibility() viewpoint.components.visibility = bcf.ComponentVisibility()
viewpoint.components.visibility.default_visibility = True viewpoint.components.visibility.default_visibility = True
viewpoint.snapshot = None viewpoint.snapshot = None
self.bcf.add_viewpoint(topic, viewpoint) self.bcf.add_viewpoint(topic, viewpoint)
except: # except:
pass # pass
def flush(self): def flush(self):
"""Saves the BCF report to file. Triggered at the end of the validation process.""" """Saves the BCF report to file. Triggered at the end of the validation process."""
if not self.filepath: 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")): 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) self.bcf.save_project(self.filepath)
+99 -66
View File
@@ -26,7 +26,19 @@ import ifcopenshell
from ifcopenshell import ids 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): class TestIdsParsing(unittest.TestCase):
@@ -212,7 +224,6 @@ class TestIdsAuthoring(unittest.TestCase):
i = ids.ids() i = ids.ids()
i.specifications.append(ids.specification(name="Test_Specification")) i.specifications.append(ids.specification(name="Test_Specification"))
i.specifications[0].add_applicability(ids.entity.create(name="Test_Name")) 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") r = ids.restriction.create(options="(Wanddurchbruch|Deckendurchbruch).*", type="pattern", base="string")
p = ids.property.create(location="any", propertyset="Test", name="Test", value=r) p = ids.property.create(location="any", propertyset="Test", name="Test", value=r)
i.specifications[0].add_requirement(p) 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.assertEqual(i.specifications[0].requirements.terms[0].value, "Deckendurchbruch")
self.assertNotEqual(i.specifications[0].requirements.terms[0].value, "Deeckendurchbruch") 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 """ """ Saving created IDS to IDS.xml """
def test_created_ids_to_xml(self):
# def test_created_ids_to_xml(self): i = ids.ids()
# i = ids.ids() i.specifications.append(ids.specification(name="Test_Specification"))
# i.specifications.append(ids.specification(name="Test_Specification")) e = ids.entity.create(name="Test_Name", predefinedtype="Test_PredefinedType")
# e = ids.entity.create(name="Test_Name", predefinedtype="Test_PredefinedType") c = ids.classification.create(location="any", value="Test_Value", system="Test_System")
# c = ids.classification.create(location="any", value="Test_Value", system="Test_System") m = ids.material.create(location="any", value="Test_Value")
# m = ids.material.create(location="any", value="Test_Value") re = ids.restriction.create(options=["testA", "testB"], type="enumeration", base="string")
# re = ids.restriction.create(options=["testA", "testB"], type="enumeration", base="string") rb = ids.restriction.create(options={"minInclusive": 0, "maxExclusive": 10}, type="bounds", base="integer")
# 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")
# rp = 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) 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) 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) p3 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=rp1)
# p4 = ids.property.create( p4 = ids.property.create(location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=rp2)
# location="any", propertyset="Test_PropertySet", name="Test_Parameter", value=[re, rb, rp] 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_applicability(e)
# i.specifications[0].add_requirement(c) i.specifications[0].add_applicability(m)
# i.specifications[0].add_requirement(p1) i.specifications[0].add_requirement(c)
# i.specifications[0].add_requirement(p2) i.specifications[0].add_requirement(p1)
# i.specifications[0].add_requirement(p3) #TODO i.specifications[0].add_requirement(p2)
# i.specifications[0].add_requirement(p4) i.specifications[0].add_requirement(p3)
# fn = "TEST_FILE.xml" i.specifications[0].add_requirement(p4)
# result = i.to_xml(fn) #TODO i.specifications[0].add_requirement(p5)
# os.remove(fn) fn = "TEST_FILE.xml"
# self.assertTrue(result) result = i.to_xml(fn)
os.remove(fn)
self.assertTrue(result)
""" IDS information """ """ IDS information """
@@ -268,13 +289,39 @@ class TestIdsAuthoring(unittest.TestCase):
class TestIfcValidation(unittest.TestCase): class TestIfcValidation(unittest.TestCase):
def test_validate_simple(self):
# TODO
pass
def test_validate_all_facets(self): def test_validate_simple(self):
# TODO #Same test as in reporting...
pass 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 """ """ Validating IDS files with restrictions """
@@ -307,39 +354,25 @@ class TestIfcValidation(unittest.TestCase):
# # self.assertTrue( ) # # self.assertTrue( )
# class TestIdsReporting(unittest.TestCase): class TestIdsReporting(unittest.TestCase):
# TEST_PATH = os.path.join(os.path.dirname(__file__), "test.ifc") def test_simple_report(self):
# IFC_URL = os.path.join(os.path.dirname(__file__), "Sample-BIM-Files/IFC/", "IFC4_Wall_3_with_properties.ifc") #Same test as in validation...
# IDS_URL = os.path.join(os.path.dirname(__file__), "Sample-BIM-Files/IDS/", "IDS_Wall_needs_all_fields.xml") ids_file = ids.ids.open(IDS_URL)
report = ids.SimpleHandler()
# logger = logging.getLogger("IDS_Logger") logger.addHandler(report)
# # logging.basicConfig(level=logging.INFO, format="%(message)s") ids_file.validate(ifc_file, logger)
# # logging.basicConfig(filename=os.path.join(os.path.dirname(__file__), "log.txt"), level=logging.INFO, format="%(message)s") self.assertEqual(len(report.statements), 5)
# content = IFC_URL def test_bcf_report(self):
# file = open(TEST_PATH, "w") ids_file = ids.ids.open(IDS_URL)
# file.write(content) fn = os.path.join(tempfile.gettempdir(), "test.bcf")
# file.close() bcf_handler = ids.BcfHandler(project_name="Default IDS Project", author="your@email.com", filepath=fn)
# ifc_file = ifcopenshell.open(TEST_PATH) logger.addHandler(bcf_handler)
# os.remove(TEST_PATH) ids_file.validate(ifc_file, logger)
my_bcfxml = bcfxml.load(fn)
# def test_simple_report(self): topics = my_bcfxml.get_topics()
# ids_file = ids.ids.open(self.IDS_URL) self.assertEqual(len(topics), 5)
# 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)
if __name__ == "__main__": if __name__ == "__main__":