From d294be3433e9e102ac1612270916661c00be9b0b Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 5 Jun 2024 14:53:31 +1000 Subject: [PATCH] Support adding a child space using the rules of spatial decomposition for each schema --- .../blenderbim/bim/module/aggregate/data.py | 7 -- .../bim/module/aggregate/operator.py | 6 +- .../blenderbim/bim/module/aggregate/ui.py | 13 ---- .../blenderbim/bim/module/spatial/data.py | 76 ++++++++++++++++--- .../blenderbim/bim/module/spatial/prop.py | 11 ++- .../blenderbim/bim/module/spatial/ui.py | 31 +++++--- src/blenderbim/blenderbim/tool/spatial.py | 12 +-- 7 files changed, 99 insertions(+), 57 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/data.py b/src/blenderbim/blenderbim/bim/module/aggregate/data.py index eb4f8a2504..8eef8b00bd 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/data.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/data.py @@ -36,7 +36,6 @@ class AggregateData: "relating_object_label": cls.get_relating_object_label(), "has_related_objects": cls.has_related_objects(), "total_parts": cls.total_parts(), - "ifc_class": cls.ifc_class(), "total_linked_aggregate": cls.total_linked_aggregate(), } cls.is_loaded = True @@ -68,12 +67,6 @@ class AggregateData: def has_related_objects(cls) -> bool: return bool(cls.get_related_objects()) - @classmethod - def ifc_class(cls) -> str: - element = tool.Ifc.get_entity(bpy.context.active_object) - if element: - return element.is_a() - @classmethod def total_linked_aggregate(cls): element = tool.Ifc.get_entity(bpy.context.active_object) diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py index f36462a9e7..a719cea549 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/operator.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/operator.py @@ -275,23 +275,23 @@ class BIM_OT_add_part_to_object(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} part_class: bpy.props.StringProperty(name="Class", options={"HIDDEN"}) part_name: bpy.props.StringProperty(name="Name") - obj: bpy.props.StringProperty(options={"HIDDEN"}) + element: bpy.props.IntProperty(options={"HIDDEN"}) def invoke(self, context, event): self.part_name = "My " + self.part_class.lstrip("Ifc") return context.window_manager.invoke_props_dialog(self) def _execute(self, context): - obj = bpy.data.objects.get(self.obj) or context.active_object core.add_part_to_object( tool.Ifc, tool.Aggregate, tool.Collector, tool.Blender, - obj=obj, + obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.element)) if self.element else context.active_object, part_class=self.part_class, part_name=self.part_name, ) + tool.Spatial.load_container_manager() class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, Operator): diff --git a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py index 35a1e8161c..ff2292baad 100644 --- a/src/blenderbim/blenderbim/bim/module/aggregate/ui.py +++ b/src/blenderbim/blenderbim/bim/module/aggregate/ui.py @@ -99,19 +99,6 @@ class BIM_PT_aggregate(Panel): op = row.operator("bim.select_parts", icon="RESTRICT_SELECT_OFF", text="") op.obj = context.active_object.name - ifc_class = AggregateData.data["ifc_class"] - part_class = "" - if ifc_class == "IfcBuilding": - part_class = "IfcBuildingStorey" - elif ifc_class == "IfcSite": - part_class = "IfcBuilding" - elif ifc_class == "IfcProject": - part_class = "IfcSite" - if part_class != "": - op = layout.operator("bim.add_part_to_object", text="Add " + part_class.lstrip("Ifc")) - op.part_class = part_class - op.obj = context.active_object.name - class BIM_PT_linked_aggregate(Panel): bl_label = "Linked Aggregates" diff --git a/src/blenderbim/blenderbim/bim/module/spatial/data.py b/src/blenderbim/blenderbim/bim/module/spatial/data.py index 6aa9d8f945..b31545860d 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/data.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/data.py @@ -92,19 +92,71 @@ class ProjectTreeData: def load(cls): cls.is_loaded = True cls.data = { - "containers": cls.containers(), + "subelement_class": cls.subelement_class(), } @classmethod - def containers(cls): - results = {} + def subelement_class(cls): + results = [] + props = bpy.context.scene.BIMProjectTreeProperties + if not (container := props.active_container): + return results + container_class = tool.Ifc.get().by_id(container.ifc_definition_id).is_a() if tool.Ifc.get_schema() == "IFC2X3": - spatial_elements = tool.Ifc.get().by_type("IfcSpatialStructureElement") - else: - spatial_elements = tool.Ifc.get().by_type("IfcSpatialElement") - for container in spatial_elements: - results[container.id()] = { - "type": container.is_a(), - "id": container.id(), - } - return results + results = { + "IfcBuilding": ["IfcBuilding", "IfcBuildingStorey", "IfcSpace"], + "IfcBuildingStorey": ["IfcBuildingStorey", "IfcSpace"], + "IfcProject": ["IfcBuilding", "IfcSite", "IfcSpace"], + "IfcSite": ["IfcBuilding", "IfcSite", "IfcSpace"], + "IfcSpace": ["IfcSpace"], + }[container_class] + elif tool.Ifc.get_schema() == "IFC4": + results = { + "IfcBuilding": ["IfcBuilding", "IfcBuildingStorey", "IfcSpace"], + "IfcBuildingStorey": ["IfcBuildingStorey", "IfcSpace"], + "IfcExternalSpatialElement": ["IfcExternalSpatialElement"], + "IfcProject": ["IfcBuilding", "IfcExternalSpatialElement", "IfcSite", "IfcSpace"], + "IfcSite": ["IfcBuilding", "IfcExternalSpatialElement", "IfcSite", "IfcSpace"], + "IfcSpace": ["IfcSpace"], + }[container_class] + elif tool.Ifc.get_schema() == "IFC4X3": + results = { + "IfcBridge": ["IfcBridge", "IfcBridgePart", "IfcSpace"], + "IfcBridgePart": ["IfcSpace"], + "IfcBuilding": ["IfcBuilding", "IfcBuildingStorey", "IfcSpace"], + "IfcBuildingStorey": ["IfcBuildingStorey", "IfcSpace"], + "IfcExternalSpatialElement": ["IfcExternalSpatialElement"], + "IfcFacility": ["IfcFacility", "IfcFacilityPartCommon", "IfcSpace"], + "IfcFacilityPartCommon": ["IfcSpace"], + "IfcMarineFacility": ["IfcMarineFacility", "IfcMarinePart", "IfcSpace"], + "IfcMarinePart": ["IfcSpace"], + "IfcProject": [ + "IfcBridge", + "IfcBuilding", + "IfcExternalSpatialElement", + "IfcFacility", + "IfcMarineFacility", + "IfcRailway", + "IfcRoad", + "IfcSite", + "IfcSpace", + ], + "IfcRailway": ["IfcRailway", "IfcRailwayPart", "IfcSpace"], + "IfcRailwayPart": ["IfcSpace"], + "IfcRoad": ["IfcRoad", "IfcRoadPart", "IfcSpace"], + "IfcRoadPart": ["IfcSpace"], + "IfcSite": [ + "IfcBridge", + "IfcBuilding", + "IfcExternalSpatialElement", + "IfcFacility", + "IfcMarineFacility", + "IfcRailway", + "IfcRoad", + "IfcSite", + "IfcSpace", + ], + "IfcSpace": ["IfcSpace"], + }[container_class] + + return [(r, r, "") for r in results] diff --git a/src/blenderbim/blenderbim/bim/module/spatial/prop.py b/src/blenderbim/blenderbim/bim/module/spatial/prop.py index 93eb0bfea2..b4586b01a0 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/prop.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/prop.py @@ -18,7 +18,7 @@ import bpy from blenderbim.bim.prop import StrProperty, Attribute -from blenderbim.bim.module.spatial.data import SpatialData +from blenderbim.bim.module.spatial.data import ProjectTreeData from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -31,10 +31,15 @@ from bpy.props import ( CollectionProperty, ) import blenderbim.tool as tool -import blenderbim.core.geometry import ifcopenshell +def get_subelement_class(self, context): + if not ProjectTreeData.is_loaded: + ProjectTreeData.load() + return ProjectTreeData.data["subelement_class"] + + def update_elevation(self, context): if ifc_definition_id := self.ifc_definition_id: entity = tool.Ifc.get().by_id(ifc_definition_id) @@ -50,6 +55,7 @@ def update_name(self, context): def update_active_container_index(self, context): + ProjectTreeData.data["subelement_class"] = ProjectTreeData.subelement_class() tool.Spatial.load_contained_elements() @@ -124,6 +130,7 @@ class BIMProjectTreeProperties(PropertyGroup): elements: CollectionProperty(name="Elements", type=Element) active_element_index: IntProperty(name="Active Element Index") total_elements: IntProperty(name="Total Elements") + subelement_class: bpy.props.EnumProperty(items=get_subelement_class, name="Subelement Class") @property def active_container(self): diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index 1a32843a7b..9593cbeea1 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -112,15 +112,23 @@ class BIM_PT_project_tree(Panel): if self.props.active_container: row = self.layout.row(align=True) row.label( - text=f"Active {self.props.active_container.ifc_class}: {self.props.active_container.name}", + text=f"Active: {self.props.active_container.name}", icon="OUTLINER_COLLECTION", ) - row.operator("bim.select_decomposed_elements", icon="HIDE_OFF", text="") - row.operator("bim.delete_container", icon="X", text="").container = ( - self.props.active_container.ifc_definition_id - ) row.operator("bim.load_container_manager", icon="FILE_REFRESH", text="") + if self.props.active_container.ifc_class != "IfcProject": + row = self.layout.row(align=True) + row.operator("bim.select_decomposed_elements", icon="HIDE_OFF", text=f"Isolate {self.props.active_container.ifc_class}") + row.operator("bim.delete_container", icon="X", text="").container = ( + self.props.active_container.ifc_definition_id + ) + + row = self.layout.row(align=True) + row.prop(self.props, "subelement_class", text="") + op = row.operator("bim.add_part_to_object", icon="ADD", text="") + op.element = self.props.active_container.ifc_definition_id + op.part_class = self.props.subelement_class else: row = self.layout.row(align=True) row.label(text="Warning: No Active Container", icon="ERROR") @@ -139,10 +147,6 @@ class BIM_PT_project_tree(Panel): if not self.props.active_container: return - # spatial_data = ProjectTreeData.data["containers"].get(ifc_definition_id, None) - # if spatial_data and spatial_data["type"] in ["IfcBuildingStorey", "IfcBuilding"]: - # row.operator("bim.add_building_storey", icon="ADD", text="Add storey").part_class = "IfcBuildingStorey" - if not self.props.total_elements: row = self.layout.row() row.label(text="No Contained Elements", icon="FILE_3D") @@ -168,7 +172,8 @@ class BIM_UL_containers_manager(UIList): row = layout.row(align=True) self.draw_hierarchy(row, item) row.prop(item, "name", emboss=False, text="") - row.prop(item, "long_name", emboss=False, text="") + if item.long_name: + row.prop(item, "long_name", emboss=False, text="") col = row.column() col.alignment = "RIGHT" col.prop(item, "elevation", emboss=False, text="") @@ -187,7 +192,9 @@ class BIM_UL_containers_manager(UIList): ) else: row.label(text="", icon="BLANK1") - if item.ifc_class == "IfcSite": + if item.ifc_class == "IfcProject": + row.label(text="", icon="FILE") + elif item.ifc_class == "IfcSite": row.label(text="", icon="WORLD") elif item.ifc_class == "IfcBuilding": row.label(text="", icon="HOME") @@ -195,6 +202,8 @@ class BIM_UL_containers_manager(UIList): row.label(text="", icon="LINENUMBERS_OFF") elif item.ifc_class == "IfcSpace": row.label(text="", icon="ANTIALIASED") + elif "Part" in item.ifc_class: + row.label(text="", icon="MOD_FLUID") else: row.label(text="", icon="META_PLANE") diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index 23d1076d21..a0fc21364d 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -246,14 +246,7 @@ class Spatial(blenderbim.core.tool.Spatial): previous_container_index = props.active_container_index props.containers.clear() cls.contracted_containers = json.loads(props.contracted_containers) - props.is_container_update_enabled = False - parent = tool.Ifc.get().by_type("IfcProject")[0] - - for subelement in ifcopenshell.util.element.get_parts(parent) or []: - if subelement.is_a("IfcSpatialElement") or subelement.is_a("IfcSpatialStructureElement"): - cls.import_spatial_structure(subelement, 0) - props.is_container_update_enabled = True - # triggers spatial manager props setup + cls.import_spatial_structure(tool.Ifc.get().by_type("IfcProject")[0], 0) props.active_container_index = min(previous_container_index, len(props.containers) - 1) @classmethod @@ -264,7 +257,8 @@ class Spatial(blenderbim.core.tool.Spatial): new.name = element.Name or "Unnamed" new.description = element.Description or "" new.long_name = element.LongName or "" - new.elevation = ifcopenshell.util.placement.get_storey_elevation(element) + if not element.is_a("IfcProject"): + new.elevation = ifcopenshell.util.placement.get_storey_elevation(element) new.is_expanded = element.id() not in cls.contracted_containers new.level_index = level_index children = ifcopenshell.util.element.get_parts(element)