Support adding a child space using the rules of spatial decomposition for each schema

This commit is contained in:
Dion Moult
2024-06-05 14:53:31 +10:00
parent 60ae4e80ce
commit d294be3433
7 changed files with 99 additions and 57 deletions
@@ -36,7 +36,6 @@ class AggregateData:
"relating_object_label": cls.get_relating_object_label(), "relating_object_label": cls.get_relating_object_label(),
"has_related_objects": cls.has_related_objects(), "has_related_objects": cls.has_related_objects(),
"total_parts": cls.total_parts(), "total_parts": cls.total_parts(),
"ifc_class": cls.ifc_class(),
"total_linked_aggregate": cls.total_linked_aggregate(), "total_linked_aggregate": cls.total_linked_aggregate(),
} }
cls.is_loaded = True cls.is_loaded = True
@@ -68,12 +67,6 @@ class AggregateData:
def has_related_objects(cls) -> bool: def has_related_objects(cls) -> bool:
return bool(cls.get_related_objects()) 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 @classmethod
def total_linked_aggregate(cls): def total_linked_aggregate(cls):
element = tool.Ifc.get_entity(bpy.context.active_object) element = tool.Ifc.get_entity(bpy.context.active_object)
@@ -275,23 +275,23 @@ class BIM_OT_add_part_to_object(bpy.types.Operator, Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
part_class: bpy.props.StringProperty(name="Class", options={"HIDDEN"}) part_class: bpy.props.StringProperty(name="Class", options={"HIDDEN"})
part_name: bpy.props.StringProperty(name="Name") part_name: bpy.props.StringProperty(name="Name")
obj: bpy.props.StringProperty(options={"HIDDEN"}) element: bpy.props.IntProperty(options={"HIDDEN"})
def invoke(self, context, event): def invoke(self, context, event):
self.part_name = "My " + self.part_class.lstrip("Ifc") self.part_name = "My " + self.part_class.lstrip("Ifc")
return context.window_manager.invoke_props_dialog(self) return context.window_manager.invoke_props_dialog(self)
def _execute(self, context): def _execute(self, context):
obj = bpy.data.objects.get(self.obj) or context.active_object
core.add_part_to_object( core.add_part_to_object(
tool.Ifc, tool.Ifc,
tool.Aggregate, tool.Aggregate,
tool.Collector, tool.Collector,
tool.Blender, 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_class=self.part_class,
part_name=self.part_name, part_name=self.part_name,
) )
tool.Spatial.load_container_manager()
class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, Operator): class BIM_OT_break_link_to_other_aggregates(bpy.types.Operator, Operator):
@@ -99,19 +99,6 @@ class BIM_PT_aggregate(Panel):
op = row.operator("bim.select_parts", icon="RESTRICT_SELECT_OFF", text="") op = row.operator("bim.select_parts", icon="RESTRICT_SELECT_OFF", text="")
op.obj = context.active_object.name 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): class BIM_PT_linked_aggregate(Panel):
bl_label = "Linked Aggregates" bl_label = "Linked Aggregates"
@@ -92,19 +92,71 @@ class ProjectTreeData:
def load(cls): def load(cls):
cls.is_loaded = True cls.is_loaded = True
cls.data = { cls.data = {
"containers": cls.containers(), "subelement_class": cls.subelement_class(),
} }
@classmethod @classmethod
def containers(cls): def subelement_class(cls):
results = {} 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": if tool.Ifc.get_schema() == "IFC2X3":
spatial_elements = tool.Ifc.get().by_type("IfcSpatialStructureElement") results = {
else: "IfcBuilding": ["IfcBuilding", "IfcBuildingStorey", "IfcSpace"],
spatial_elements = tool.Ifc.get().by_type("IfcSpatialElement") "IfcBuildingStorey": ["IfcBuildingStorey", "IfcSpace"],
for container in spatial_elements: "IfcProject": ["IfcBuilding", "IfcSite", "IfcSpace"],
results[container.id()] = { "IfcSite": ["IfcBuilding", "IfcSite", "IfcSpace"],
"type": container.is_a(), "IfcSpace": ["IfcSpace"],
"id": container.id(), }[container_class]
} elif tool.Ifc.get_schema() == "IFC4":
return results 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]
@@ -18,7 +18,7 @@
import bpy import bpy
from blenderbim.bim.prop import StrProperty, Attribute 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.types import PropertyGroup
from bpy.props import ( from bpy.props import (
PointerProperty, PointerProperty,
@@ -31,10 +31,15 @@ from bpy.props import (
CollectionProperty, CollectionProperty,
) )
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.core.geometry
import ifcopenshell 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): def update_elevation(self, context):
if ifc_definition_id := self.ifc_definition_id: if ifc_definition_id := self.ifc_definition_id:
entity = tool.Ifc.get().by_id(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): def update_active_container_index(self, context):
ProjectTreeData.data["subelement_class"] = ProjectTreeData.subelement_class()
tool.Spatial.load_contained_elements() tool.Spatial.load_contained_elements()
@@ -124,6 +130,7 @@ class BIMProjectTreeProperties(PropertyGroup):
elements: CollectionProperty(name="Elements", type=Element) elements: CollectionProperty(name="Elements", type=Element)
active_element_index: IntProperty(name="Active Element Index") active_element_index: IntProperty(name="Active Element Index")
total_elements: IntProperty(name="Total Elements") total_elements: IntProperty(name="Total Elements")
subelement_class: bpy.props.EnumProperty(items=get_subelement_class, name="Subelement Class")
@property @property
def active_container(self): def active_container(self):
@@ -112,15 +112,23 @@ class BIM_PT_project_tree(Panel):
if self.props.active_container: if self.props.active_container:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label( 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", 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="") 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: else:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text="Warning: No Active Container", icon="ERROR") 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: if not self.props.active_container:
return 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: if not self.props.total_elements:
row = self.layout.row() row = self.layout.row()
row.label(text="No Contained Elements", icon="FILE_3D") row.label(text="No Contained Elements", icon="FILE_3D")
@@ -168,7 +172,8 @@ class BIM_UL_containers_manager(UIList):
row = layout.row(align=True) row = layout.row(align=True)
self.draw_hierarchy(row, item) self.draw_hierarchy(row, item)
row.prop(item, "name", emboss=False, text="") 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 = row.column()
col.alignment = "RIGHT" col.alignment = "RIGHT"
col.prop(item, "elevation", emboss=False, text="") col.prop(item, "elevation", emboss=False, text="")
@@ -187,7 +192,9 @@ class BIM_UL_containers_manager(UIList):
) )
else: else:
row.label(text="", icon="BLANK1") 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") row.label(text="", icon="WORLD")
elif item.ifc_class == "IfcBuilding": elif item.ifc_class == "IfcBuilding":
row.label(text="", icon="HOME") row.label(text="", icon="HOME")
@@ -195,6 +202,8 @@ class BIM_UL_containers_manager(UIList):
row.label(text="", icon="LINENUMBERS_OFF") row.label(text="", icon="LINENUMBERS_OFF")
elif item.ifc_class == "IfcSpace": elif item.ifc_class == "IfcSpace":
row.label(text="", icon="ANTIALIASED") row.label(text="", icon="ANTIALIASED")
elif "Part" in item.ifc_class:
row.label(text="", icon="MOD_FLUID")
else: else:
row.label(text="", icon="META_PLANE") row.label(text="", icon="META_PLANE")
+3 -9
View File
@@ -246,14 +246,7 @@ class Spatial(blenderbim.core.tool.Spatial):
previous_container_index = props.active_container_index previous_container_index = props.active_container_index
props.containers.clear() props.containers.clear()
cls.contracted_containers = json.loads(props.contracted_containers) cls.contracted_containers = json.loads(props.contracted_containers)
props.is_container_update_enabled = False cls.import_spatial_structure(tool.Ifc.get().by_type("IfcProject")[0], 0)
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
props.active_container_index = min(previous_container_index, len(props.containers) - 1) props.active_container_index = min(previous_container_index, len(props.containers) - 1)
@classmethod @classmethod
@@ -264,7 +257,8 @@ class Spatial(blenderbim.core.tool.Spatial):
new.name = element.Name or "Unnamed" new.name = element.Name or "Unnamed"
new.description = element.Description or "" new.description = element.Description or ""
new.long_name = element.LongName 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.is_expanded = element.id() not in cls.contracted_containers
new.level_index = level_index new.level_index = level_index
children = ifcopenshell.util.element.get_parts(element) children = ifcopenshell.util.element.get_parts(element)