diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 59707e65f6..08b9f565bf 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -1602,108 +1602,13 @@ class IfcImporter: obj.BIMObjectProperties.collection = self.collections[project.GlobalId] = self.project["blender"] def create_collections(self) -> None: - self.create_spatial_decomposition_collections() - if self.ifc_import_settings.collection_mode == "DECOMPOSITION": - self.create_aggregate_and_nest_collections() - elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION": - pass - - def create_spatial_decomposition_collections(self) -> None: - for rel_aggregate in self.project["ifc"].IsDecomposedBy or []: - self.create_spatial_decomposition_collection(self.project["blender"], rel_aggregate.RelatedObjects) - - # Invalid IFCs may have orphaned spatial structure elements. - orphaned_spaces = [e for e in self.spatial_elements if e.GlobalId not in self.collections] - while orphaned_spaces: - self.create_spatial_decomposition_collection(self.project["blender"], orphaned_spaces) - orphaned_spaces = [e for e in self.spatial_elements if e.GlobalId not in self.collections] - + for element in self.spatial_elements: + collection = bpy.data.collections.new(tool.Loader.get_name(element)) + self.collections[element.GlobalId] = collection + self.project["blender"].children.link(collection) tool.Loader.create_project_collection("Views") self.type_collection = tool.Loader.create_project_collection("Types") - def create_spatial_decomposition_collection( - self, parent: Union[bpy.types.Collection, bpy.types.Object], related_objects: list[ifcopenshell.entity_instance] - ) -> None: - for element in related_objects: - if element not in self.spatial_elements: - continue - is_existing = False - if self.has_existing_project: - obj = tool.Ifc.get_object(element) - if obj: - is_existing = True - collection = obj.BIMObjectProperties.collection - self.collections[element.GlobalId] = collection - if not is_existing: - collection = bpy.data.collections.new(tool.Loader.get_name(element)) - self.collections[element.GlobalId] = collection - parent.children.link(collection) - if element.IsDecomposedBy: - for rel_aggregate in element.IsDecomposedBy: - self.create_spatial_decomposition_collection(collection, rel_aggregate.RelatedObjects) - - def create_aggregate_and_nest_collections(self): - if self.ifc_import_settings.has_filter: - rel_aggregates = set() - for element in self.elements: - if decomposed_by := element.IsDecomposedBy: - rel_aggregates.add(decomposed_by[0]) - elif decomposes := element.Decomposes: - rel_aggregates.add(decomposes[0]) - elif nested_by := getattr(element, "IsNestedBy", []): # IFC2X3 does not have IsNestedBy - if next((e for e in nested_by[0].RelatedObjects if not e.is_a("IfcPort")), None): - rel_aggregates.add(nested_by[0]) - elif nests := getattr(element, "Nests", []): - rel_aggregates.add(nests[0]) - elif element.is_a("IfcSurfaceFeature") and self.file.schema == "IFC4X3": - rel_aggregates.add(element.AdheresToElement[0]) - else: - rel_aggregates = [ - r - for r in self.file.by_type("IfcRelAggregates") - if (relating_obj := r.RelatingObject).is_a("IfcElement") or relating_obj.is_a("IfcElementType") - ] + [ - r - for r in self.file.by_type("IfcRelNests") - if ( - (relating_obj := r.RelatingObject).is_a("IfcElement") - or relating_obj.is_a("IfcElementType") - or (relating_obj.is_a("IfcPositioningElement") and not relating_obj.is_a("IfcGrid")) - ) - and [e for e in r.RelatedObjects if not e.is_a("IfcPort")] - ] - if self.file.schema == "IFC4X3": - rel_aggregates += [r for r in self.file.by_type("IfcRelAdheresToElement")] - - if len(rel_aggregates) > 10000: - # More than 10,000 collections makes Blender unhappy - print("Skipping aggregate collections for performance.") - self.ifc_import_settings.collection_mode = "SPATIAL_DECOMPOSITION" - return - - aggregates: dict[str, dict] = {} - for rel_aggregate in rel_aggregates: - element: ifcopenshell.entity_instance = getattr(rel_aggregate, "RelatingObject", None) or getattr( - rel_aggregate, "RelatingElement" - ) - collection = bpy.data.collections.new(tool.Loader.get_name(element)) - aggregates[element.GlobalId] = {"element": element, "collection": collection} - self.collections[element.GlobalId] = collection - - for global_id, aggregate in aggregates.items(): - parent = ifcopenshell.util.element.get_aggregate(aggregate["element"]) - if parent: - self.collections[parent.GlobalId].children.link(aggregate["collection"]) - continue - parent = ifcopenshell.util.element.get_container(aggregate["element"]) - if parent: - self.collections[parent.GlobalId].children.link(aggregate["collection"]) - continue - if aggregate["element"].is_a("IfcElementType"): - self.type_collection.children.link(aggregate["collection"]) - continue - self.project["blender"].children.link(aggregate["collection"]) - def create_materials(self) -> None: for material in self.file.by_type("IfcMaterial"): self.create_material(material) @@ -1763,14 +1668,6 @@ class IfcImporter: self.place_object_in_collection(self.file.by_id(ifc_definition_id), obj) def place_object_in_collection(self, element: ifcopenshell.entity_instance, obj: bpy.types.Object) -> None: - if self.ifc_import_settings.collection_mode == "DECOMPOSITION": - self.place_object_in_decomposition_collection(element, obj) - elif self.ifc_import_settings.collection_mode == "SPATIAL_DECOMPOSITION": - self.place_object_in_spatial_decomposition_collection(element, obj) - - def place_object_in_decomposition_collection( - self, element: ifcopenshell.entity_instance, obj: bpy.types.Object - ) -> None: if element.is_a("IfcProject"): return elif element.is_a("IfcGridAxis"): @@ -1781,51 +1678,18 @@ class IfcImporter: obj.BIMObjectProperties.collection = collection collection.name = obj.name return collection.objects.link(obj) - elif getattr(element, "Decomposes", None): - aggregate = ifcopenshell.util.element.get_aggregate(element) - return self.collections[aggregate.GlobalId].objects.link(obj) - elif getattr(element, "Nests", None) and not element.is_a("IfcPort"): - nest = ifcopenshell.util.element.get_nest(element) - return self.collections[nest.GlobalId].objects.link(obj) - elif element.is_a("IfcSurfaceFeature") and self.file.schema == "IFC4X3": - adherend = element.AdheresToElement[0].RelatingElement - return self.collections[adherend.GlobalId].objects.link(obj) - - return self.place_object_in_spatial_decomposition_collection(element, obj) - - def place_object_in_spatial_decomposition_collection( - self, element: ifcopenshell.entity_instance, obj: bpy.types.Object - ) -> None: - if element.is_a("IfcProject"): - return - elif element.is_a("IfcGridAxis"): - return - elif element.GlobalId in self.collections: - collection = self.collections[element.GlobalId] - collection.BIMCollectionProperties.obj = obj - obj.BIMObjectProperties.collection = collection - return collection.objects.link(obj) elif element.is_a("IfcTypeObject"): return self.type_collection.objects.link(obj) elif element.is_a("IfcStructuralMember"): return self.structural_member_collection.objects.link(obj) elif element.is_a("IfcStructuralConnection"): return self.structural_connection_collection.objects.link(obj) + elif container := ifcopenshell.util.element.get_container(element): + self.collections[container.GlobalId].objects.link(obj) elif element.is_a("IfcAnnotation"): group = self.get_drawing_group(element) if group: return self.collections[group.GlobalId].objects.link(obj) - - container = ifcopenshell.util.element.get_container(element) - if container: - if element.is_a("IfcGrid"): # TODO: refactor into a more holistic collection mode feature - grid_collection = bpy.data.collections.get(obj.name) - if grid_collection: # Just in case we run into invalid grids from Revit - self.collections[container.GlobalId].children.link(grid_collection) - grid_collection.objects.link(obj) - else: - self.collections[container.GlobalId].objects.link(obj) - else: self.ifc_import_settings.logger.warning("Warning: this object is outside the spatial hierarchy %s", element) bpy.context.scene.collection.objects.link(obj) @@ -2068,7 +1932,6 @@ class IfcImportSettings: self.should_filter_spatial_elements = True self.should_setup_viewport_camera = True self.elements: set[ifcopenshell.entity_instance] = set() - self.collection_mode = "DECOMPOSITION" @staticmethod def factory(context=None, input_file=None, logger=None): @@ -2080,7 +1943,6 @@ class IfcImportSettings: logger = logging.getLogger("ImportIFC") settings.logger = logger settings.diff_file = scene_diff.diff_json_file - settings.collection_mode = props.collection_mode settings.should_use_cpu_multiprocessing = props.should_use_cpu_multiprocessing settings.merge_mode = props.merge_mode settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour diff --git a/src/blenderbim/blenderbim/bim/module/project/prop.py b/src/blenderbim/blenderbim/bim/module/project/prop.py index d56392fe31..99e9654a2f 100644 --- a/src/blenderbim/blenderbim/bim/module/project/prop.py +++ b/src/blenderbim/blenderbim/bim/module/project/prop.py @@ -125,15 +125,6 @@ class BIMProjectProperties(PropertyGroup): library_breadcrumb: CollectionProperty(name="Library Breadcrumb", type=StrProperty) library_elements: CollectionProperty(name="Library Elements", type=LibraryElement) active_library_element_index: IntProperty(name="Active Library Element Index") - collection_mode: bpy.props.EnumProperty( - items=[ - ("DECOMPOSITION", "Decomposition", "Collections represent aggregates and spatial containers"), - ("SPATIAL_DECOMPOSITION", "Spatial Decomposition", "Collections represent spatial containers"), - ("IFC_CLASS", "IFC Class", "Collections represent IFC class"), - ("NONE", "None", "No collections are created"), - ], - name="Collection Mode", - ) filter_mode: bpy.props.EnumProperty( items=[ ("NONE", "None", "No filtering is performed"), diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py index 752f2d6eab..7a72d4bcca 100644 --- a/src/blenderbim/blenderbim/bim/module/project/ui.py +++ b/src/blenderbim/blenderbim/bim/module/project/ui.py @@ -104,7 +104,6 @@ class BIM_PT_project(Panel): def draw_load_ui(self, context): pprops = context.scene.BIMProjectProperties - prop_with_search(self.layout, pprops, "collection_mode") prop_with_search(self.layout, pprops, "filter_mode") if pprops.filter_mode in ["DECOMPOSITION", "IFC_CLASS", "IFC_TYPE"]: row = self.layout.row(align=True) diff --git a/src/blenderbim/blenderbim/tool/collector.py b/src/blenderbim/blenderbim/tool/collector.py index 9491fcccb3..425a40ecf2 100644 --- a/src/blenderbim/blenderbim/tool/collector.py +++ b/src/blenderbim/blenderbim/tool/collector.py @@ -17,13 +17,11 @@ # along with BlenderBIM Add-on. If not, see . import bpy -import ifcopenshell.util.element - import blenderbim.core.tool import blenderbim.core.spatial import blenderbim.core.aggregate import blenderbim.tool as tool -from typing import Union +import ifcopenshell.util.element class Collector(blenderbim.core.tool.Collector): @@ -33,164 +31,52 @@ class Collector(blenderbim.core.tool.Collector): and unlink them from any other""" element = tool.Ifc.get_entity(obj) - object_collection = None - collection_collection = None + if element.is_a("IfcGridAxis"): + element = (element.PartOfU or element.PartOfV or element.PartOfW)[0] - object_collection = cls._get_own_collection(element, obj) - if object_collection: - collection_collection = cls._get_collection(element, obj) - else: - object_collection = cls._get_collection(element, obj) - - # NOTE: calling `obj.users_collection` is expensive in large projects - # since it's iterating over all collections under the hood - - # ensure object is linked only to object_collection - if obj.users_collection != (object_collection,): - for collection in obj.users_collection: - collection.objects.unlink(obj) - if object_collection is not None: - object_collection.objects.link(obj) - - # ensure object_collection is linked only to collection_collection - if collection_collection and collection_collection.children.find(object_collection.name) == -1: - if bpy.context.scene.collection.children.find(object_collection.name) != -1: - bpy.context.scene.collection.children.unlink(object_collection) - for collection in bpy.data.collections: - if collection.children.find(object_collection.name) != -1: - collection.children.unlink(object_collection) - collection_collection.children.link(object_collection) - - # If an aggregate or nested host loses all its children, it no longer needs its own collection - if obj.BIMObjectProperties.collection and obj.BIMObjectProperties.collection != object_collection: - bpy.data.collections.remove(obj.BIMObjectProperties.collection) - - @classmethod - def _get_own_collection( - cls, element: ifcopenshell.entity_instance, obj: bpy.types.Object - ) -> Union[bpy.types.Collection, None]: - """get or create own collection for the element if it's neccessary for it's type""" if element.is_a("IfcProject"): - return cls._create_own_collection(obj) - - if tool.Ifc.get_schema() == "IFC2X3": - if element.is_a("IfcSpatialStructureElement"): - return cls._create_own_collection(obj) - else: - if element.is_a("IfcSpatialStructureElement") or element.is_a("IfcExternalSpatialStructureElement"): - return cls._create_own_collection(obj) - - if element.is_a("IfcGrid"): - return cls._create_own_collection(obj) - - if element.is_a("IfcGridAxis"): - if element.PartOfU: - grid = element.PartOfU[0] - axes = "UAxes" - elif element.PartOfV: - grid = element.PartOfV[0] - axes = "VAxes" - elif element.PartOfW: - grid = element.PartOfW[0] - axes = "WAxes" - grid_obj = tool.Ifc.get_object(grid) - if grid_obj: - grid_col = cls._get_own_collection(grid, grid_obj) - axes_col = next((c for c in grid_col.children if axes in c.name), None) - return axes_col or bpy.data.collections.new(axes) - - if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING": - return cls._create_own_collection(obj) - - if element.is_a("IfcStructuralMember"): - return bpy.data.collections.get("Members") or bpy.data.collections.new("Members") - - if element.is_a("IfcStructuralConnection"): - return bpy.data.collections.get("Connections") or bpy.data.collections.new("Connections") - - if getattr(element, "IsDecomposedBy", None): - return cls._create_own_collection(obj) - - if getattr(element, "IsNestedBy", None): - if any(e for e in element.IsNestedBy[0].RelatedObjects if not e.is_a("IfcPort")): - return cls._create_own_collection(obj) - - if getattr(element, "HasSurfaceFeatures", None): - return cls._create_own_collection(obj) - - @classmethod - def _get_collection(cls, element: ifcopenshell.entity_instance, obj: bpy.types.Object) -> bpy.types.Collection: - """get or create collection for the element based on it's type""" - if element.is_a("IfcTypeObject"): - return cls._create_project_child_collection("Types") - - if element.is_a("IfcOpeningElement"): - return cls._create_project_child_collection("IfcOpeningElements") - - if element.is_a("IfcGridAxis"): - if element.PartOfU: - grid = element.PartOfU[0] - axes = "UAxes" - elif element.PartOfV: - grid = element.PartOfV[0] - axes = "VAxes" - elif element.PartOfW: - grid = element.PartOfW[0] - axes = "WAxes" - grid_obj = tool.Ifc.get_object(grid) - if grid_obj: - return grid_obj.BIMObjectProperties.collection - - if element.is_a("IfcAnnotation"): - if element.ObjectType == "DRAWING": - return cls._create_project_child_collection("Views") - for rel in element.HasAssignments or []: - if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING": - for related_object in rel.RelatedObjects: - if related_object.is_a("IfcAnnotation") and related_object.ObjectType == "DRAWING": - drawing_obj = tool.Ifc.get_object(related_object) - if drawing_obj: - return drawing_obj.BIMObjectProperties.collection - - if element.is_a("IfcStructuralItem"): - return cls._create_project_child_collection("StructuralItems") - - aggregate = ifcopenshell.util.element.get_aggregate(element) - if aggregate: - aggregate_obj = tool.Ifc.get_object(aggregate) - if aggregate_obj: - collection = aggregate_obj.BIMObjectProperties.collection - if collection: - return collection - - nest = ifcopenshell.util.element.get_nest(element) - if nest: - nest_obj = tool.Ifc.get_object(nest) - if nest_obj: - collection = nest_obj.BIMObjectProperties.collection - if collection: - return collection - - container = ifcopenshell.util.element.get_container(element) - if container: + if collection := cls._create_own_collection(obj): + collection.objects.link(obj) + bpy.context.scene.collection.children.link(collection) + elif element.is_a("IfcTypeProduct"): + collection = cls._create_project_child_collection("IfcTypeProduct") + collection.objects.link(obj) + elif element.is_a("IfcOpeningElement"): + collection = cls._create_project_child_collection("IfcOpeningElement") + collection.objects.link(obj) + elif element.is_a("IfcStructuralItem"): + collection = cls._create_project_child_collection("IfcStructuralItem") + collection.objects.link(obj) + elif tool.Ifc.get_schema() == "IFC2X3" and element.is_a("IfcSpatialStructureElement"): + if collection := cls._create_own_collection(obj): + collection.objects.link(obj) + project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0]) + project_obj.BIMObjectProperties.collection.children.link(collection) + elif tool.Ifc.get_schema() != "IFC2X3" and element.is_a("IfcSpatialElement"): + if collection := cls._create_own_collection(obj): + collection.objects.link(obj) + project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0]) + project_obj.BIMObjectProperties.collection.children.link(collection) + elif container := ifcopenshell.util.element.get_container(element): container_obj = tool.Ifc.get_object(container) - collection = container_obj.BIMObjectProperties.collection - if collection: - return collection - - if element.is_a("IfcSurfaceFeature") and element.file.schema == "IFC4X3": - adherend = element.AdheresToElement[0].RelatingElement - adherend_obj = tool.Ifc.get_object(adherend) - collection = adherend_obj.BIMObjectProperties.collection - if collection: - return collection - - if element.is_a("IfcProject"): - return bpy.context.scene.collection - - project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0]) - if project_obj: - return project_obj.BIMObjectProperties.collection + container_obj.BIMObjectProperties.collection.objects.link(obj) + elif element.is_a("IfcAnnotation"): + if element.ObjectType == "DRAWING": + if collection := cls._create_own_collection(obj): + collection.objects.link(obj) + project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0]) + project_obj.BIMObjectProperties.collection.children.link(collection) + else: + for rel in element.HasAssignments or []: + if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.ObjectType == "DRAWING": + for related_object in rel.RelatedObjects: + if related_object.is_a("IfcAnnotation") and related_object.ObjectType == "DRAWING": + drawing_obj = tool.Ifc.get_object(related_object) + if drawing_obj: + drawing_obj.BIMObjectProperties.collection.objects.link(obj) + else: + collection = cls._create_project_child_collection("Unsorted") + collection.objects.link(obj) @classmethod def _create_project_child_collection(cls, name: str) -> bpy.types.Collection: @@ -207,7 +93,7 @@ class Collector(blenderbim.core.tool.Collector): def _create_own_collection(cls, obj: bpy.types.Object) -> bpy.types.Collection: """get or create own collection for the element""" if obj.BIMObjectProperties.collection: - return obj.BIMObjectProperties.collection + return collection = bpy.data.collections.new(obj.name) obj.BIMObjectProperties.collection = collection collection.BIMCollectionProperties.obj = obj