From 92adeca837679885b6724b467d5886c878296dd9 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 12 Sep 2021 17:29:11 +1000 Subject: [PATCH] Fix bug where aggregates break with filtered elements during import. --- src/blenderbim/blenderbim/bim/ifc.py | 3 ++ src/blenderbim/blenderbim/bim/import_ifc.py | 32 +++++++++-------- .../blenderbim/bim/module/project/operator.py | 16 +++++++-- src/blenderbim/blenderbim/bim/prop.py | 4 +-- .../test/bim/module/project/test_operator.py | 24 ++++++++----- src/blenderbim/test/files/basic.ifc | 35 +++++++++++++++++-- 6 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/ifc.py b/src/blenderbim/blenderbim/bim/ifc.py index 62c029be53..b5879fce39 100644 --- a/src/blenderbim/blenderbim/bim/ifc.py +++ b/src/blenderbim/blenderbim/bim/ifc.py @@ -18,8 +18,11 @@ import bpy import uuid +import zipfile +import tempfile import ifcopenshell import blenderbim.bim.handler +from pathlib import Path class IfcStore: diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 33b380d821..90018d3f79 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -211,10 +211,6 @@ class IfcImporter: self.profile_code("Process element filter") self.create_collections() self.profile_code("Create collections") - self.create_aggregates() - self.profile_code("Create aggregates") - self.create_aggregate_tree() - self.profile_code("Create aggregate tree") self.create_openings_collection() self.profile_code("Create opening collection") self.create_materials() @@ -978,15 +974,16 @@ class IfcImporter: self.project["blender"].objects.link(obj) def create_collections(self): - if self.ifc_import_settings.collection_mode == "DECOMPOSITION" and len(self.file.by_type("IfcRelAggregates")) > 10000: - # More than 10,000 collections makes Blender unhappy - print("Falling back to SPATIAL_DECOMPOSITION collection mode") - self.ifc_import_settings.collection_mode = "SPATIAL_DECOMPOSITION" - if self.ifc_import_settings.collection_mode == "DECOMPOSITION": self.create_decomposition_collections() + elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION": + self.create_spatial_decomposition_collections() def create_decomposition_collections(self): + self.create_spatial_decomposition_collections() + self.create_aggregate_collections() + + def create_spatial_decomposition_collections(self): containers = set([ifcopenshell.util.element.get_container(e) for e in self.elements]) self.decomposition_containers = set() for container in containers: @@ -1014,18 +1011,23 @@ class IfcImporter: for rel_aggregate in element.IsDecomposedBy: self.add_related_objects(collection, rel_aggregate.RelatedObjects) + def create_aggregate_collections(self): + self.create_aggregates() + self.create_aggregate_tree() + def create_aggregates(self): if self.ifc_import_settings.has_filter: - rel_aggregates = [e.IsDecomposedBy[0].RelatingObject for e in self.elements if e.IsDecomposedBy] + rel_aggregates = [e.IsDecomposedBy[0] for e in self.elements if e.IsDecomposedBy] else: rel_aggregates = [a for a in self.file.by_type("IfcRelAggregates") if a.RelatingObject.is_a("IfcElement")] + if len(rel_aggregates) > 10000: # More than 10,000 collections makes Blender unhappy - print("Falling back to SPATIAL_DECOMPOSITION collection mode") - self.ifc_import_settings.collection_mode = "SPATIAL_DECOMPOSITION" - else: - for rel_aggregate in rel_aggregates: - self.create_aggregate(rel_aggregate) + print("Skipping aggregate collections for performance.") + return + + for rel_aggregate in rel_aggregates: + self.create_aggregate(rel_aggregate) def create_aggregate_tree(self): for aggregate in self.aggregates.values(): diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 06a81bc1f4..12fdfb2ff3 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -514,8 +514,9 @@ class LoadProject(bpy.types.Operator): filter_glob: bpy.props.StringProperty(default="*.ifc;*.ifczip;*.ifcxml", options={"HIDDEN"}) def execute(self, context): - if os.path.exists(self.filepath) and "ifc" in os.path.splitext(self.filepath)[1]: - context.scene.BIMProperties.ifc_file = self.filepath + if not os.path.exists(self.filepath) or "ifc" not in os.path.splitext(self.filepath)[1].lower(): + return {"FINISHED"} + context.scene.BIMProperties.ifc_file = self.filepath context.scene.BIMProjectProperties.is_loading = True return {"FINISHED"} @@ -583,8 +584,19 @@ class LoadProjectElements(bpy.types.Operator): for container in containers: for rel in container.ContainsElements: elements.update(rel.RelatedElements) + self.append_decomposed_elements(elements) return elements + def append_decomposed_elements(self, elements): + decomposed_elements = set() + for element in elements: + if element.IsDecomposedBy: + for subelement in element.IsDecomposedBy[0].RelatedObjects: + decomposed_elements.add(subelement) + if decomposed_elements: + self.append_decomposed_elements(decomposed_elements) + elements.update(decomposed_elements) + def get_ifc_class_elements(self): elements = set() for filter_category in self.props.filter_categories: diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index 481b4234f7..5c90d32e93 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -64,7 +64,7 @@ def updateDataDir(self, context): blenderbim.bim.schema.ifc.data_dir = context.scene.BIMProperties.data_dir -def updateIfcFile(self, context): +def update_ifc_file(self, context): if context.scene.BIMProperties.ifc_file: blenderbim.bim.handler.loadIfcStore(context.scene) @@ -221,7 +221,7 @@ class BIMProperties(PropertyGroup): data_dir: StringProperty( default=os.path.join(cwd, "data") + os.path.sep, name="Data Directory", update=updateDataDir ) - ifc_file: StringProperty(name="IFC File", update=updateIfcFile) + ifc_file: StringProperty(name="IFC File", update=update_ifc_file) export_schema: EnumProperty(items=[("IFC4", "IFC4", ""), ("IFC2X3", "IFC2X3", "")], name="IFC Schema") last_transaction: StringProperty(name="Last Transaction") contexts: EnumProperty(items=getContexts, name="Contexts") diff --git a/src/blenderbim/test/bim/module/project/test_operator.py b/src/blenderbim/test/bim/module/project/test_operator.py index 806e4b4542..08fffc6f07 100644 --- a/src/blenderbim/test/bim/module/project/test_operator.py +++ b/src/blenderbim/test/bim/module/project/test_operator.py @@ -61,10 +61,14 @@ class TestLoadProjectElements(test.bim.bootstrap.NewFile): And the object "IfcBuildingStorey/Level 1" is an "IfcBuildingStorey" And the object "IfcSlab/Slab" is an "IfcSlab" And the object "IfcWall/Wall" is an "IfcWall" + And the object "IfcElementAssembly/Empty" is an "IfcElementAssembly" + And the object "IfcBeam/Beam" is an "IfcBeam" And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" And the object "IfcBuildingStorey/Ground Floor" is in the collection "IfcBuildingStorey/Ground Floor" And the object "IfcBuildingStorey/Level 1" is in the collection "IfcBuildingStorey/Level 1" + And the object "IfcElementAssembly/Empty" is in the collection "IfcElementAssembly/Empty" + And the object "IfcBeam/Beam" is in the collection "IfcElementAssembly/Empty" And the object "IfcSlab/Slab" is in the collection "IfcBuildingStorey/Ground Floor" And the object "IfcWall/Wall" is in the collection "IfcBuildingStorey/Level 1" And "scene.BIMProjectProperties.is_loading" is "False" @@ -79,20 +83,24 @@ class TestLoadProjectElements(test.bim.bootstrap.NewFile): Then "scene.BIMProjectProperties.filter_categories['IfcSite/My Site'].total_elements" is "0" Then "scene.BIMProjectProperties.filter_categories['IfcBuilding/My Building'].total_elements" is "0" Then "scene.BIMProjectProperties.filter_categories['IfcBuildingStorey/Ground Floor'].total_elements" is "1" - Then "scene.BIMProjectProperties.filter_categories['IfcBuildingStorey/Level 1'].total_elements" is "1" - When I set "scene.BIMProjectProperties.filter_categories['IfcBuildingStorey/Ground Floor'].is_selected" to "True" + Then "scene.BIMProjectProperties.filter_categories['IfcBuildingStorey/Level 1'].total_elements" is "2" + When I set "scene.BIMProjectProperties.filter_categories['IfcBuildingStorey/Level 1'].is_selected" to "True" And I press "bim.load_project_elements" Then the object "IfcProject/My Project" is an "IfcProject" And the object "IfcSite/My Site" is an "IfcSite" And the object "IfcBuilding/My Building" is an "IfcBuilding" - And the object "IfcBuildingStorey/Ground Floor" is an "IfcBuildingStorey" - And the object "IfcSlab/Slab" is an "IfcSlab" + And the object "IfcBuildingStorey/Level 1" is an "IfcBuildingStorey" + And the object "IfcWall/Wall" is an "IfcWall" + And the object "IfcElementAssembly/Empty" is an "IfcElementAssembly" + And the object "IfcBeam/Beam" is an "IfcBeam" And the object "IfcSite/My Site" is in the collection "IfcSite/My Site" And the object "IfcBuilding/My Building" is in the collection "IfcBuilding/My Building" - And the object "IfcBuildingStorey/Ground Floor" is in the collection "IfcBuildingStorey/Ground Floor" - And the object "IfcSlab/Slab" is in the collection "IfcBuildingStorey/Ground Floor" - And the object "IfcBuildingStorey/Level 1" does not exist - And the object "IfcWall/Wall" does not exist + And the object "IfcBuildingStorey/Level 1" is in the collection "IfcBuildingStorey/Level 1" + And the object "IfcWall/Wall" is in the collection "IfcBuildingStorey/Level 1" + And the object "IfcElementAssembly/Empty" is in the collection "IfcElementAssembly/Empty" + And the object "IfcBeam/Beam" is in the collection "IfcElementAssembly/Empty" + And the object "IfcBuildingStorey/Ground Floor" does not exist + And the object "IfcSlab/Slab" does not exist """ @test.bim.bootstrap.scenario diff --git a/src/blenderbim/test/files/basic.ifc b/src/blenderbim/test/files/basic.ifc index f239509bdf..ac80e3976f 100644 --- a/src/blenderbim/test/files/basic.ifc +++ b/src/blenderbim/test/files/basic.ifc @@ -1,7 +1,7 @@ ISO-10303-21; HEADER; FILE_DESCRIPTION(('ViewDefinition[DesignTransferView]'),'2;1'); -FILE_NAME('basic.ifc','2021-09-11T19:44:13+10:00',(),(),'IfcOpenShell 0.6.0b0','BlenderBIM 0.0.999999','Nobody'); +FILE_NAME('basic.ifc','2021-09-12T16:48:33+10:00',(),(),'IfcOpenShell 0.6.0b0','BlenderBIM 0.0.999999','Nobody'); FILE_SCHEMA(('IFC4')); ENDSEC; DATA; @@ -110,8 +110,8 @@ DATA; #133=IFCSHAPEREPRESENTATION(#22,'Box','BoundingBox',(#132)); #134=IFCPRODUCTDEFINITIONSHAPE($,$,(#133,#130)); #135=IFCSTYLEDITEM(#129,(#101),'Material'); -#136=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631353443,#9,#8,1631353443); -#137=IFCRELCONTAINEDINSPATIALSTRUCTURE('1CgivZt6z1l8IgKaTkZMcT',#136,$,$,(#116),#71); +#136=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631429228,#9,#8,1631353443); +#137=IFCRELCONTAINEDINSPATIALSTRUCTURE('1CgivZt6z1l8IgKaTkZMcT',#136,$,$,(#116,#159),#71); #148=IFCCARTESIANPOINT((0.,0.,3.)); #149=IFCDIRECTION((0.,0.,1.)); #150=IFCDIRECTION((1.,0.,0.)); @@ -122,5 +122,34 @@ DATA; #155=IFCDIRECTION((1.,0.,0.)); #156=IFCAXIS2PLACEMENT3D(#153,#154,#155); #157=IFCLOCALPLACEMENT(#152,#156); +#158=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631429175,#9,#8,1631429175); +#159=IFCELEMENTASSEMBLY('122brjtWrFAPe7NkNdFNlG',#158,'Empty',$,$,#174,$,$,$,.ACCESSORY_ASSEMBLY.); +#170=IFCCARTESIANPOINT((0.,0.,0.)); +#171=IFCDIRECTION((0.,0.,1.)); +#172=IFCDIRECTION((1.,0.,0.)); +#173=IFCAXIS2PLACEMENT3D(#170,#171,#172); +#174=IFCLOCALPLACEMENT(#152,#173); +#175=IFCOWNERHISTORY(#9,#8,.READWRITE.,.MODIFIED.,1631429290,#9,#8,1631429215); +#176=IFCBEAM('2f_ancUNT1$Btb3bI4H7gM',#175,'Beam',$,$,#216,#194,$,.BEAM.); +#182=IFCINDEXEDPOLYGONALFACE((1,2,4,3)); +#183=IFCINDEXEDPOLYGONALFACE((3,4,8,7)); +#184=IFCINDEXEDPOLYGONALFACE((7,8,6,5)); +#185=IFCINDEXEDPOLYGONALFACE((5,6,2,1)); +#186=IFCINDEXEDPOLYGONALFACE((3,7,5,1)); +#187=IFCINDEXEDPOLYGONALFACE((8,4,2,6)); +#188=IFCCARTESIANPOINTLIST3D(((-1.,-1.,-1.),(-1.,-1.,1.),(-1.,1.,-1.),(-1.,1.,1.),(1.,-1.,-1.),(1.,-1.,1.),(1.,1.,-1.),(1.,1.,1.))); +#189=IFCPOLYGONALFACESET(#188,$,(#182,#183,#184,#185,#186,#187),$); +#190=IFCSHAPEREPRESENTATION(#21,'Body','Tessellation',(#189)); +#191=IFCCARTESIANPOINT((-1.,-1.,-1.)); +#192=IFCBOUNDINGBOX(#191,2.,2.,2.); +#193=IFCSHAPEREPRESENTATION(#22,'Box','BoundingBox',(#192)); +#194=IFCPRODUCTDEFINITIONSHAPE($,$,(#193,#190)); +#205=IFCOWNERHISTORY(#9,#8,.READWRITE.,.ADDED.,1631429238,#9,#8,1631429238); +#206=IFCRELAGGREGATES('1SGr7ReC13EhuetdWMr2G9',#205,$,$,#159,(#176)); +#212=IFCCARTESIANPOINT((0.,0.,-3.)); +#213=IFCDIRECTION((0.,0.,1.)); +#214=IFCDIRECTION((1.,0.,0.)); +#215=IFCAXIS2PLACEMENT3D(#212,#213,#214); +#216=IFCLOCALPLACEMENT(#174,#215); ENDSEC; END-ISO-10303-21;