mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-16 21:42:19 +00:00
Fix #5230. Reimplement spatial decomposition hierarchy.
This commit is contained in:
@@ -64,6 +64,10 @@ def update_should_include_children(self, context):
|
|||||||
tool.Spatial.load_contained_elements()
|
tool.Spatial.load_contained_elements()
|
||||||
|
|
||||||
|
|
||||||
|
def update_element_mode(self, context):
|
||||||
|
tool.Spatial.load_contained_elements()
|
||||||
|
|
||||||
|
|
||||||
def update_container_obj(self, context):
|
def update_container_obj(self, context):
|
||||||
if self.container_obj is None or not (obj := context.active_object):
|
if self.container_obj is None or not (obj := context.active_object):
|
||||||
return
|
return
|
||||||
@@ -112,6 +116,8 @@ class Element(PropertyGroup):
|
|||||||
name="IFC Definition ID",
|
name="IFC Definition ID",
|
||||||
description="ID of the element type / occurrence. 0 if 'type' is 'CLASS' or if it's 'TYPE' but it represents untyped elements",
|
description="ID of the element type / occurrence. 0 if 'type' is 'CLASS' or if it's 'TYPE' but it represents untyped elements",
|
||||||
)
|
)
|
||||||
|
level: IntProperty(name="Level", default=0)
|
||||||
|
has_children: BoolProperty(name="Has Children", default=False)
|
||||||
total: IntProperty(name="Total")
|
total: IntProperty(name="Total")
|
||||||
is_expanded: BoolProperty(name="Is Expanded", default=False)
|
is_expanded: BoolProperty(name="Is Expanded", default=False)
|
||||||
type: EnumProperty(
|
type: EnumProperty(
|
||||||
@@ -134,6 +140,14 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
|
|||||||
expanded_elements: StringProperty(name="Expanded Elements", default="{}")
|
expanded_elements: StringProperty(name="Expanded Elements", default="{}")
|
||||||
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")
|
||||||
|
element_mode: EnumProperty(
|
||||||
|
name="Element Mode",
|
||||||
|
items=(
|
||||||
|
("TYPE", "Class > Type > Occurrence", "Show a breakdown by IfcClass, relating type, and occurrence"),
|
||||||
|
("DECOMPOSITION", "Element Decomposition", "Show a breakdown by element decomposition"),
|
||||||
|
),
|
||||||
|
update=update_element_mode,
|
||||||
|
)
|
||||||
subelement_class: bpy.props.EnumProperty(items=get_subelement_class, name="Subelement Class")
|
subelement_class: bpy.props.EnumProperty(items=get_subelement_class, name="Subelement Class")
|
||||||
default_container: IntProperty(name="Default Container", default=0)
|
default_container: IntProperty(name="Default Container", default=0)
|
||||||
should_include_children: BoolProperty(
|
should_include_children: BoolProperty(
|
||||||
|
|||||||
@@ -160,8 +160,11 @@ class BIM_PT_spatial_decomposition(Panel):
|
|||||||
if not self.props.total_elements:
|
if not self.props.total_elements:
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.label(text=f"{self.props.active_container.ifc_class} > No Elements", icon="FILE_3D")
|
row.label(text=f"{self.props.active_container.ifc_class} > No Elements", icon="FILE_3D")
|
||||||
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
|
|
||||||
row.operator("bim.assign_container", icon="FOLDER_REDIRECT", text="").container = ifc_definition_id
|
row.operator("bim.assign_container", icon="FOLDER_REDIRECT", text="").container = ifc_definition_id
|
||||||
|
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.prop(self.props, "element_mode", text="", icon="FILEBROWSER")
|
||||||
|
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
|
||||||
return
|
return
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
@@ -169,7 +172,6 @@ class BIM_PT_spatial_decomposition(Panel):
|
|||||||
text=f"{self.props.active_container.ifc_class} > {self.props.total_elements} Elements",
|
text=f"{self.props.active_container.ifc_class} > {self.props.total_elements} Elements",
|
||||||
icon="FILE_3D",
|
icon="FILE_3D",
|
||||||
)
|
)
|
||||||
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
|
|
||||||
row.operator("bim.assign_container", icon="FOLDER_REDIRECT", text="").container = ifc_definition_id
|
row.operator("bim.assign_container", icon="FOLDER_REDIRECT", text="").container = ifc_definition_id
|
||||||
op = row.operator("bim.select_decomposed_element", icon="OBJECT_DATA", text="")
|
op = row.operator("bim.select_decomposed_element", icon="OBJECT_DATA", text="")
|
||||||
if active_element := self.props.active_element:
|
if active_element := self.props.active_element:
|
||||||
@@ -179,6 +181,10 @@ class BIM_PT_spatial_decomposition(Panel):
|
|||||||
op = row.operator("bim.select_decomposed_elements", icon="RESTRICT_SELECT_OFF", text="")
|
op = row.operator("bim.select_decomposed_elements", icon="RESTRICT_SELECT_OFF", text="")
|
||||||
op.container = ifc_definition_id
|
op.container = ifc_definition_id
|
||||||
|
|
||||||
|
row = self.layout.row(align=True)
|
||||||
|
row.prop(self.props, "element_mode", text="", icon="FILEBROWSER")
|
||||||
|
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
|
||||||
|
|
||||||
self.layout.template_list(
|
self.layout.template_list(
|
||||||
"BIM_UL_elements",
|
"BIM_UL_elements",
|
||||||
"",
|
"",
|
||||||
@@ -273,23 +279,26 @@ class BIM_UL_elements(UIList):
|
|||||||
if item:
|
if item:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
item_type = item.type
|
item_type = item.type
|
||||||
if item_type == "CLASS":
|
for _ in range(item.level):
|
||||||
|
row.label(text="", icon="BLANK1")
|
||||||
|
if item.has_children:
|
||||||
self.draw_toggle(row, item.is_expanded, index)
|
self.draw_toggle(row, item.is_expanded, index)
|
||||||
|
if item_type == "CLASS":
|
||||||
row.label(text=item.name)
|
row.label(text=item.name)
|
||||||
col = row.column()
|
col = row.column()
|
||||||
col.alignment = "RIGHT"
|
col.alignment = "RIGHT"
|
||||||
col.label(text=str(item.total))
|
col.label(text=str(item.total))
|
||||||
elif item_type == "TYPE":
|
elif item_type == "TYPE":
|
||||||
row.label(text="", icon="BLANK1")
|
|
||||||
self.draw_toggle(row, item.is_expanded, index)
|
|
||||||
row.label(text=item.name)
|
row.label(text=item.name)
|
||||||
col = row.column()
|
col = row.column()
|
||||||
col.alignment = "RIGHT"
|
col.alignment = "RIGHT"
|
||||||
col.label(text=str(item.total))
|
col.label(text=str(item.total))
|
||||||
else: # OCCURRENCE
|
elif item_type == "OCCURRENCE":
|
||||||
for _ in range(2):
|
|
||||||
row.label(text="", icon="BLANK1")
|
|
||||||
row.label(text=item.name)
|
row.label(text=item.name)
|
||||||
|
if item.total:
|
||||||
|
col = row.column()
|
||||||
|
col.alignment = "RIGHT"
|
||||||
|
col.label(text=str(item.total))
|
||||||
|
|
||||||
def draw_filter(self, context, layout):
|
def draw_filter(self, context, layout):
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
|
|||||||
@@ -226,6 +226,14 @@ class Spatial(bonsai.core.tool.Spatial):
|
|||||||
return
|
return
|
||||||
|
|
||||||
container = tool.Ifc.get().by_id(container.ifc_definition_id)
|
container = tool.Ifc.get().by_id(container.ifc_definition_id)
|
||||||
|
if props.element_mode == "TYPE":
|
||||||
|
cls.load_contained_elements_by_type(container)
|
||||||
|
elif props.element_mode == "DECOMPOSITION":
|
||||||
|
cls.load_contained_elements_by_decomposition(container)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_contained_elements_by_type(cls, container: ifcopenshell.entity_instance) -> None:
|
||||||
|
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
||||||
results = cls.get_grouped_elements_in_container(container)
|
results = cls.get_grouped_elements_in_container(container)
|
||||||
|
|
||||||
expanded_elements = json.loads(props.expanded_elements)
|
expanded_elements = json.loads(props.expanded_elements)
|
||||||
@@ -239,6 +247,7 @@ class Spatial(bonsai.core.tool.Spatial):
|
|||||||
new = props.elements.add()
|
new = props.elements.add()
|
||||||
new.name = ifc_class
|
new.name = ifc_class
|
||||||
new.type = "CLASS"
|
new.type = "CLASS"
|
||||||
|
new.has_children = True
|
||||||
class_is_expanded = ifc_class in expanded_classes
|
class_is_expanded = ifc_class in expanded_classes
|
||||||
class_is_expanded_r = ifc_class in expanded_classes_r
|
class_is_expanded_r = ifc_class in expanded_classes_r
|
||||||
new.is_expanded = class_is_expanded or class_is_expanded_r
|
new.is_expanded = class_is_expanded or class_is_expanded_r
|
||||||
@@ -251,6 +260,8 @@ class Spatial(bonsai.core.tool.Spatial):
|
|||||||
if new.is_expanded:
|
if new.is_expanded:
|
||||||
new2 = props.elements.add()
|
new2 = props.elements.add()
|
||||||
new2.type = "TYPE"
|
new2.type = "TYPE"
|
||||||
|
new2.has_children = True
|
||||||
|
new2.level = 1
|
||||||
new2.name = type_data["type_name"]
|
new2.name = type_data["type_name"]
|
||||||
new2.ifc_class = ifc_class
|
new2.ifc_class = ifc_class
|
||||||
new2.total = total2
|
new2.total = total2
|
||||||
@@ -265,6 +276,7 @@ class Spatial(bonsai.core.tool.Spatial):
|
|||||||
for element in type_data["elements"]:
|
for element in type_data["elements"]:
|
||||||
occurrence = props.elements.add()
|
occurrence = props.elements.add()
|
||||||
occurrence.name = element.Name or "Unnamed"
|
occurrence.name = element.Name or "Unnamed"
|
||||||
|
occurrence.level = 2
|
||||||
occurrence.ifc_definition_id = element.id()
|
occurrence.ifc_definition_id = element.id()
|
||||||
occurrence.type = "OCCURRENCE"
|
occurrence.type = "OCCURRENCE"
|
||||||
|
|
||||||
@@ -274,6 +286,35 @@ class Spatial(bonsai.core.tool.Spatial):
|
|||||||
|
|
||||||
props.total_elements = total_elements
|
props.total_elements = total_elements
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_contained_elements_by_decomposition(cls, container: ifcopenshell.entity_instance) -> None:
|
||||||
|
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
||||||
|
expanded_elements = json.loads(props.expanded_elements)
|
||||||
|
expanded_ifc_ids = expanded_elements.get("IFC_ID", [])
|
||||||
|
|
||||||
|
def add_elements(elements, level=0):
|
||||||
|
for element in sorted(elements, key=lambda x: f"{x.is_a()}/{x.Name or 'Unnamed'}"):
|
||||||
|
ifc_definition_id = element.id()
|
||||||
|
name = f"{element.is_a()}/{element.Name or 'Unnamed'}"
|
||||||
|
new = props.elements.add()
|
||||||
|
new.name = name
|
||||||
|
new.level = level
|
||||||
|
new.ifc_definition_id = ifc_definition_id
|
||||||
|
new.type = "OCCURRENCE"
|
||||||
|
children = [
|
||||||
|
e
|
||||||
|
for e in ifcopenshell.util.element.get_decomposition(element, is_recursive=False)
|
||||||
|
if not e.is_a("IfcFeatureElement")
|
||||||
|
]
|
||||||
|
if children:
|
||||||
|
new.has_children = True
|
||||||
|
new.total = len(children)
|
||||||
|
if ifc_definition_id in expanded_ifc_ids:
|
||||||
|
add_elements(children, level=level + 1)
|
||||||
|
|
||||||
|
elements = ifcopenshell.util.element.get_decomposition(container, is_recursive=False)
|
||||||
|
add_elements(elements)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def filter_elements(
|
def filter_elements(
|
||||||
cls,
|
cls,
|
||||||
@@ -373,7 +414,14 @@ class Spatial(bonsai.core.tool.Spatial):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def toggle_container_element(cls, element_index: int, is_recursive: bool) -> None:
|
def toggle_container_element(cls, element_index: int, is_recursive: bool) -> None:
|
||||||
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
||||||
|
if props.element_mode == "TYPE":
|
||||||
|
cls.toggle_container_element_by_type(element_index, is_recursive)
|
||||||
|
elif props.element_mode == "DECOMPOSITION":
|
||||||
|
cls.toggle_container_element_by_decomposition(element_index, is_recursive)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def toggle_container_element_by_type(cls, element_index: int, is_recursive: bool) -> None:
|
||||||
|
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
||||||
expanded_elements: dict[str, list[Union[str, int]]] = json.loads(props.expanded_elements)
|
expanded_elements: dict[str, list[Union[str, int]]] = json.loads(props.expanded_elements)
|
||||||
|
|
||||||
element = props.elements[element_index]
|
element = props.elements[element_index]
|
||||||
@@ -420,6 +468,39 @@ class Spatial(bonsai.core.tool.Spatial):
|
|||||||
|
|
||||||
props.expanded_elements = json.dumps(expanded_elements)
|
props.expanded_elements = json.dumps(expanded_elements)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def toggle_container_element_by_decomposition(cls, element_index: int, is_recursive: bool) -> None:
|
||||||
|
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
||||||
|
element = props.elements[element_index]
|
||||||
|
expanded_elements: dict[str, list[Union[str, int]]] = json.loads(props.expanded_elements)
|
||||||
|
expanded_elements_list: list[Union[str, int]] = expanded_elements.setdefault("IFC_ID", [])
|
||||||
|
ifc_definition_id = element.ifc_definition_id
|
||||||
|
if ifc_definition_id in expanded_elements_list:
|
||||||
|
expanded_elements_list.remove(ifc_definition_id)
|
||||||
|
should_expand = False
|
||||||
|
else:
|
||||||
|
expanded_elements_list.append(ifc_definition_id)
|
||||||
|
should_expand = True
|
||||||
|
|
||||||
|
if is_recursive:
|
||||||
|
queue = [tool.Ifc.get().by_id(ifc_definition_id)]
|
||||||
|
while queue:
|
||||||
|
element = queue.pop()
|
||||||
|
children = [
|
||||||
|
e
|
||||||
|
for e in ifcopenshell.util.element.get_decomposition(element, is_recursive=False)
|
||||||
|
if not e.is_a("IfcFeatureElement")
|
||||||
|
]
|
||||||
|
ifc_definition_id = element.id()
|
||||||
|
if children:
|
||||||
|
if should_expand is False and ifc_definition_id in expanded_elements_list:
|
||||||
|
expanded_elements_list.remove(ifc_definition_id)
|
||||||
|
elif should_expand is True and ifc_definition_id not in expanded_elements_list:
|
||||||
|
expanded_elements_list.append(ifc_definition_id)
|
||||||
|
queue.extend(children)
|
||||||
|
|
||||||
|
props.expanded_elements = json.dumps(expanded_elements)
|
||||||
|
|
||||||
# HERE STARTS SPATIAL TOOL
|
# HERE STARTS SPATIAL TOOL
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user