diff --git a/src/blenderbim/blenderbim/bim/module/tester/__init__.py b/src/blenderbim/blenderbim/bim/module/tester/__init__.py index aae027b711..98480fa660 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/tester/__init__.py @@ -23,8 +23,15 @@ classes = ( operator.ExecuteIfcTester, operator.SelectSpecs, operator.SelectIfcTesterIfcFile, + operator.SelectRequirement, + operator.SelectEntity, + operator.ExportBcf, + prop.FailedEntities, + prop.Specification, prop.IfcTesterProperties, ui.BIM_PT_tester, + ui.BIM_UL_tester_specifications, + ui.BIM_UL_tester_failed_entities ) @@ -34,3 +41,4 @@ def register(): def unregister(): del bpy.types.Scene.IfcTesterProperties + diff --git a/src/blenderbim/blenderbim/bim/module/tester/operator.py b/src/blenderbim/blenderbim/bim/module/tester/operator.py index 591567a338..6926009602 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/operator.py +++ b/src/blenderbim/blenderbim/bim/module/tester/operator.py @@ -21,6 +21,7 @@ import bpy import time import tempfile import webbrowser +import json import ifctester import ifctester.ids import ifctester.reporter @@ -53,13 +54,27 @@ class ExecuteIfcTester(bpy.types.Operator): print("Finished loading:", time.time() - start) start = time.time() specs.validate(ifc) + print("Finished validating:", time.time() - start) start = time.time() engine = ifctester.reporter.Html(specs) - engine.report() + engine.report() engine.to_file(output) webbrowser.open("file://" + output) + + report = None + report = ifctester.reporter.Json(specs).report()['specifications'] + if report: + props.has_report = True + props.report = json.dumps(report) + props.specifications.clear() + c=0 + for spec in report: + new_spec = props.specifications.add() + new_spec.name = spec['name'] + new_spec.status = spec['status'] + return {"FINISHED"} @@ -95,3 +110,63 @@ class SelectIfcTesterIfcFile(bpy.types.Operator): def invoke(self, context, event): context.window_manager.fileselect_add(self) return {"RUNNING_MODAL"} + + +class SelectRequirement(bpy.types.Operator): + bl_idname = "bim.select_requirement" + bl_label = "Select Specification" + bl_options = {"REGISTER", "UNDO"} + spec_index: bpy.props.IntProperty() + req_index: bpy.props.IntProperty() + + def execute(self, context): + props = context.scene.IfcTesterProperties + report = json.loads(props.report) + props.old_index = self.spec_index + failed_entities = report[self.spec_index]['requirements'] [self.req_index]['failed_entities'] + props.n_entities = len(failed_entities) + props.has_entities = True if props.n_entities > 0 else False + props.failed_entities.clear() + for e in failed_entities: + new_entity = props.failed_entities.add() + new_entity.element = e['element'] + new_entity.reason = e['reason'] + + return {"FINISHED"} + +class SelectEntity(bpy.types.Operator): + bl_idname = "bim.select_entity" + bl_label = "Select Entity" + bl_options = {"REGISTER", "UNDO"} + ifc_id: bpy.props.IntProperty() + + def execute(self, context): + bpy.ops.object.select_all(action='DESELECT') + for obj in context.scene.objects: + if obj.BIMObjectProperties.ifc_definition_id == self.ifc_id: + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + return {"FINISHED"} + +class ExportBcf(bpy.types.Operator): + bl_idname = "bim.export_bcf" + bl_label = "Export BCF" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + props = context.scene.IfcTesterProperties + with tempfile.TemporaryDirectory() as dirpath: + output = os.path.join(dirpath, "{}.bcf".format(props.specs)) + if props.should_load_from_memory and tool.Ifc.get(): + ifc = tool.Ifc.get() + else: + ifc = ifcopenshell.open(props.ifc_file) + specs = ifctester.ids.open(props.specs) + specs.validate(ifc) + bcf_reporter = ifctester.reporter.Bcf(specs) + bcf_reporter.report() + bcf_reporter.to_file(output) + print("Finished exporting:") + + return {"FINISHED"} + diff --git a/src/blenderbim/blenderbim/bim/module/tester/prop.py b/src/blenderbim/blenderbim/bim/module/tester/prop.py index 09866feed1..1d3bf03c21 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/prop.py +++ b/src/blenderbim/blenderbim/bim/module/tester/prop.py @@ -29,12 +29,38 @@ from bpy.props import ( CollectionProperty, ) - def purge(): pass +def get_failure_entities(): + return + + + +class Specification(PropertyGroup): + name: StringProperty(name="Name") + status : BoolProperty(default=False, name="Status") + +class FailedEntities(PropertyGroup): + reason: StringProperty(name="Reason") + element: StringProperty(name="Element") + class IfcTesterProperties(PropertyGroup): - specs: StringProperty(default="", name="IDS File") + specs: StringProperty(default="", name="IDS File") ifc_file: StringProperty(default="", name="IFC File") should_load_from_memory: BoolProperty(default=False, name="Load from Memory") + + active_specification_index: IntProperty(name="Active Specification Index") + active_requirement_index: IntProperty(name="Active Requirement Index") + old_index: IntProperty(name="", default=0) + active_failed_entity_index: IntProperty(name="Active Failed Entity Index") + report: StringProperty(default="", name="JSON report") + specifications: CollectionProperty(name="Specifications", type=Specification) + failed_entities: CollectionProperty(name="FailedEntities", type=FailedEntities) + has_report : BoolProperty(default=False, name="") + has_entities : BoolProperty(default=False, name="") + n_entities : IntProperty(name="", default=0) + + + diff --git a/src/blenderbim/blenderbim/bim/module/tester/ui.py b/src/blenderbim/blenderbim/bim/module/tester/ui.py index f956b28f59..1a7f466192 100644 --- a/src/blenderbim/blenderbim/bim/module/tester/ui.py +++ b/src/blenderbim/blenderbim/bim/module/tester/ui.py @@ -17,7 +17,8 @@ # along with BlenderBIM Add-on. If not, see . import blenderbim.tool as tool -from bpy.types import Panel +from bpy.types import Panel, UIList +import json class BIM_PT_tester(Panel): @@ -32,20 +33,102 @@ class BIM_PT_tester(Panel): def draw(self, context): self.layout.use_property_split = True - props = context.scene.IfcTesterProperties + self.props = context.scene.IfcTesterProperties if tool.Ifc.get(): row = self.layout.row() - row.prop(props, "should_load_from_memory") + row.prop(self.props, "should_load_from_memory") - if not tool.Ifc.get() or not props.should_load_from_memory: + if not tool.Ifc.get() or not self.props.should_load_from_memory: row = self.layout.row(align=True) - row.prop(props, "ifc_file") + row.prop(self.props, "ifc_file") row.operator("bim.select_ifctester_ifc_file", icon="FILE_FOLDER", text="") row = self.layout.row(align=True) - row.prop(props, "specs") + row.prop(self.props, "specs") row.operator("bim.select_specs", icon="FILE_FOLDER", text="") row = self.layout.row() row.operator("bim.execute_ifc_tester") + + if self.props.has_report and self.props.should_load_from_memory: + self.layout.template_list( + "BIM_UL_tester_specifications", + "", + self.props, + "specifications", + self.props, + "active_specification_index", + ) + + self.draw_editable_ui(context) + + if self.props.has_report: + row = self.layout.row() + row.operator("bim.export_bcf", text="Export BCF", icon="EXPORT") + + def draw_editable_ui(self, context): + props = context.scene.IfcTesterProperties + i = props.active_specification_index + dic_report = json.loads(props.report) + + total_successes = dic_report[i]['total_successes'] + total = dic_report[i]['total'] + percentage = dic_report[i]['percentage'] + n_requirements = len(dic_report[i]['requirements']) + + row = self.layout.row() + row.label(text = f"Passed: {total_successes}/{total} ({percentage}%)") + row = self.layout.row() + row.label(text = f"Requirements ({n_requirements}):") + c=0 + box = self.layout.box() + for req in dic_report[i]['requirements']: + row = box.row(align=True) + row.label(text=f" {c+1}. {req['description']}") + if req['status']: + row.label(text="PASS", icon="CHECKMARK") + else: + row.label(text="FAIL", icon="CANCEL") + op = row.operator("bim.select_requirement", text="", icon="LONGDISPLAY") + op.spec_index = i + op.req_index = c + c += 1 + + if props.old_index == i and props.n_entities > 0: + row = self.layout.row() + row.label(text = f"Failed entities [{props.n_entities}]:") + self.layout.template_list( + "BIM_UL_tester_failed_entities", + "", + self.props, + "failed_entities", + self.props, + "active_failed_entity_index", + ) + + + +class BIM_UL_tester_specifications(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + row.label(text=item.name, icon="WORDWRAP_ON") + if item.status: + row.label(text="PASS") + else: + row.label(text="FAIL") + +class BIM_UL_tester_failed_entities(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + ifc_file = tool.Ifc.get() + if item: + ifc_id = int(item.element[1:item.element.find('=')]) + entity = ifc_file.by_id(ifc_id) + row = layout.row(align=True) + row.label(text=f'{entity.Name} [{entity.is_a()}]') + row.label(text=item.reason) + op = row.operator("bim.select_entity", text="", icon="RESTRICT_SELECT_OFF") + op.ifc_id = entity.id() + +