mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-16 21:42:19 +00:00
Implement filtered selection in new spatial decomposition panel
This commit is contained in:
@@ -237,15 +237,36 @@ class SelectDecomposedElements(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
bl_idname = "bim.select_decomposed_elements"
|
bl_idname = "bim.select_decomposed_elements"
|
||||||
bl_label = "Select Children"
|
bl_label = "Select Children"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
should_filter: bpy.props.BoolProperty(name="Should Filter", default=True, options={"SKIP_SAVE"})
|
||||||
container: bpy.props.IntProperty()
|
container: bpy.props.IntProperty()
|
||||||
ifc_class: bpy.props.StringProperty()
|
ifc_class: bpy.props.StringProperty()
|
||||||
|
relating_type: bpy.props.IntProperty()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def description(cls, context, operator):
|
def description(cls, context, operator):
|
||||||
return f"Select all elements contained in this {operator.ifc_class}"
|
return "Select all contained elements filtered by this type" + "\nALT+CLICK to select all contained elements"
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
if event.type == "LEFTMOUSE" and event.alt:
|
||||||
|
self.should_filter = False
|
||||||
|
return self.execute(context)
|
||||||
|
|
||||||
def _execute(self, context):
|
def _execute(self, context):
|
||||||
core.select_decomposed_elements(tool.Spatial, container=tool.Ifc.get().by_id(self.container))
|
if self.should_filter:
|
||||||
|
ifc_class = self.ifc_class
|
||||||
|
relating_type = tool.Ifc.get().by_id(self.relating_type) if self.relating_type else None
|
||||||
|
is_untyped = self.relating_type == 0
|
||||||
|
else:
|
||||||
|
ifc_class = ""
|
||||||
|
relating_type = None
|
||||||
|
is_untyped = False
|
||||||
|
core.select_decomposed_elements(
|
||||||
|
tool.Spatial,
|
||||||
|
container=tool.Ifc.get().by_id(self.container),
|
||||||
|
ifc_class=ifc_class,
|
||||||
|
relating_type=relating_type,
|
||||||
|
is_untyped=is_untyped,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SetDefaultContainer(bpy.types.Operator, tool.Ifc.Operator):
|
class SetDefaultContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ class Element(PropertyGroup):
|
|||||||
name: StringProperty(name="Name")
|
name: StringProperty(name="Name")
|
||||||
is_class: BoolProperty(name="Is Class", default=False)
|
is_class: BoolProperty(name="Is Class", default=False)
|
||||||
is_type: BoolProperty(name="Is Type", default=False)
|
is_type: BoolProperty(name="Is Type", default=False)
|
||||||
|
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||||
total: IntProperty(name="Total")
|
total: IntProperty(name="Total")
|
||||||
|
|
||||||
|
|
||||||
@@ -144,3 +145,8 @@ class BIMSpatialDecompositionProperties(PropertyGroup):
|
|||||||
def active_container(self):
|
def active_container(self):
|
||||||
if self.containers and self.active_container_index < len(self.containers):
|
if self.containers and self.active_container_index < len(self.containers):
|
||||||
return self.containers[self.active_container_index]
|
return self.containers[self.active_container_index]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def active_element(self):
|
||||||
|
if self.elements and self.active_element_index < len(self.elements):
|
||||||
|
return self.elements[self.active_element_index]
|
||||||
|
|||||||
@@ -161,15 +161,20 @@ 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="No Contained Elements", icon="FILE_3D")
|
row.label(text=f"{self.props.active_container.ifc_class} > No Contained Elements", icon="FILE_3D")
|
||||||
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
|
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
|
||||||
return
|
return
|
||||||
|
|
||||||
row = self.layout.row(align=True)
|
row = self.layout.row(align=True)
|
||||||
row.label(text=f"{self.props.total_elements} Contained Elements", icon="FILE_3D")
|
row.label(text=f"{self.props.active_container.ifc_class} > {self.props.total_elements} Contained Elements", icon="FILE_3D")
|
||||||
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
|
row.prop(self.props, "should_include_children", text="", icon="OUTLINER")
|
||||||
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.ifc_class = self.props.active_container.ifc_class
|
if self.props.active_element.is_class:
|
||||||
|
op.ifc_class = self.props.active_element.name
|
||||||
|
op.relating_type = 0
|
||||||
|
elif self.props.active_element.is_type:
|
||||||
|
op.ifc_class = ""
|
||||||
|
op.relating_type = self.props.active_element.ifc_definition_id
|
||||||
op.container = ifc_definition_id
|
op.container = ifc_definition_id
|
||||||
|
|
||||||
self.layout.template_list(
|
self.layout.template_list(
|
||||||
@@ -187,7 +192,20 @@ class BIM_UL_containers_manager(UIList):
|
|||||||
if item:
|
if item:
|
||||||
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="")
|
icon = {
|
||||||
|
"IfcProject": "FILE",
|
||||||
|
"IfcSite": "WORLD",
|
||||||
|
"IfcBuilding": "HOME",
|
||||||
|
"IfcBuildingStorey": "LINENUMBERS_OFF",
|
||||||
|
"IfcSpace": "ANTIALIASED",
|
||||||
|
"IfcFacilityPart": "MOD_FLUID",
|
||||||
|
"IfcBridgePart": "MOD_FLUID",
|
||||||
|
"IfcFacilityPartCommon": "MOD_FLUID",
|
||||||
|
"IfcMarinePart": "MOD_FLUID",
|
||||||
|
"IfcRailwayPart": "MOD_FLUID",
|
||||||
|
"IfcRoadPart": "MOD_FLUID",
|
||||||
|
}.get(item.ifc_class, "META_PLANE")
|
||||||
|
row.prop(item, "name", emboss=False, text="", icon=icon)
|
||||||
if item.long_name:
|
if item.long_name:
|
||||||
row.prop(item, "long_name", emboss=False, text="")
|
row.prop(item, "long_name", emboss=False, text="")
|
||||||
col = row.column()
|
col = row.column()
|
||||||
@@ -208,24 +226,6 @@ class BIM_UL_containers_manager(UIList):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
row.label(text="", icon="BLANK1")
|
row.label(text="", icon="BLANK1")
|
||||||
if item.ifc_class == "IfcProject":
|
|
||||||
row.label(text="", icon="FILE")
|
|
||||||
else:
|
|
||||||
icon = {
|
|
||||||
"IfcSite": "WORLD",
|
|
||||||
"IfcBuilding": "HOME",
|
|
||||||
"IfcBuildingStorey": "LINENUMBERS_OFF",
|
|
||||||
"IfcSpace": "ANTIALIASED",
|
|
||||||
"IfcFacilityPart": "MOD_FLUID",
|
|
||||||
"IfcBridgePart": "MOD_FLUID",
|
|
||||||
"IfcFacilityPartCommon": "MOD_FLUID",
|
|
||||||
"IfcMarinePart": "MOD_FLUID",
|
|
||||||
"IfcRailwayPart": "MOD_FLUID",
|
|
||||||
"IfcRoadPart": "MOD_FLUID",
|
|
||||||
}.get(item.ifc_class, "META_PLANE")
|
|
||||||
op = row.operator("bim.select_decomposed_elements", icon=icon, text="", emboss=False)
|
|
||||||
op.ifc_class = item.ifc_class
|
|
||||||
op.container = item.ifc_definition_id
|
|
||||||
|
|
||||||
|
|
||||||
class BIM_UL_elements(UIList):
|
class BIM_UL_elements(UIList):
|
||||||
@@ -240,7 +240,6 @@ class BIM_UL_elements(UIList):
|
|||||||
col.label(text=str(item.total))
|
col.label(text=str(item.total))
|
||||||
elif item.is_type:
|
elif item.is_type:
|
||||||
row.label(text="", icon="BLANK1")
|
row.label(text="", icon="BLANK1")
|
||||||
row.label(text="", icon="DISCLOSURE_TRI_DOWN")
|
|
||||||
row.label(text=item.name)
|
row.label(text=item.name)
|
||||||
col = row.column()
|
col = row.column()
|
||||||
col.alignment = "RIGHT"
|
col.alignment = "RIGHT"
|
||||||
|
|||||||
@@ -149,8 +149,21 @@ def delete_container(
|
|||||||
spatial.import_spatial_decomposition()
|
spatial.import_spatial_decomposition()
|
||||||
|
|
||||||
|
|
||||||
def select_decomposed_elements(spatial: tool.Spatial, container: ifcopenshell.entity_instance) -> None:
|
def select_decomposed_elements(
|
||||||
spatial.select_products(spatial.get_decomposed_elements(container))
|
spatial: tool.Spatial,
|
||||||
|
container: ifcopenshell.entity_instance,
|
||||||
|
ifc_class: str | None = None,
|
||||||
|
relating_type: ifcopenshell.entity_instance | None = None,
|
||||||
|
is_untyped: bool = False,
|
||||||
|
) -> None:
|
||||||
|
elements = spatial.get_decomposed_elements(container)
|
||||||
|
if ifc_class:
|
||||||
|
elements = spatial.filter_elements_by_class(elements, ifc_class)
|
||||||
|
elif relating_type:
|
||||||
|
elements = spatial.filter_elements_by_relating_type(elements, relating_type)
|
||||||
|
elif is_untyped:
|
||||||
|
elements = spatial.filter_elements_by_untyped(elements)
|
||||||
|
spatial.select_products(elements)
|
||||||
|
|
||||||
|
|
||||||
def generate_space(ifc: tool.Ifc, model: tool.Model, root: tool.Root, spatial: tool.Spatial, type: tool.Type) -> None:
|
def generate_space(ifc: tool.Ifc, model: tool.Model, root: tool.Root, spatial: tool.Spatial, type: tool.Type) -> None:
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import json
|
|||||||
from math import pi
|
from math import pi
|
||||||
from mathutils import Vector, Matrix
|
from mathutils import Vector, Matrix
|
||||||
from shapely import Polygon
|
from shapely import Polygon
|
||||||
from typing import Generator, Optional, Union, Literal, Any
|
from typing import Generator, Optional, Union, Literal, Any, List
|
||||||
|
|
||||||
|
|
||||||
class Spatial(blenderbim.core.tool.Spatial):
|
class Spatial(blenderbim.core.tool.Spatial):
|
||||||
@@ -244,9 +244,10 @@ class Spatial(blenderbim.core.tool.Spatial):
|
|||||||
continue
|
continue
|
||||||
element_type = ifcopenshell.util.element.get_type(element)
|
element_type = ifcopenshell.util.element.get_type(element)
|
||||||
ifc_class = element.is_a()
|
ifc_class = element.is_a()
|
||||||
|
ifc_definition_id = element_type.id() if element_type else 0
|
||||||
type_name = element_type.Name or "Unnamed" if element_type else "Untyped"
|
type_name = element_type.Name or "Unnamed" if element_type else "Untyped"
|
||||||
results.setdefault(ifc_class, {}).setdefault(type_name, 0)
|
results.setdefault(ifc_class, {}).setdefault(ifc_definition_id, {"total": 0, "type_name": type_name})
|
||||||
results[ifc_class][type_name] += 1
|
results[ifc_class][ifc_definition_id]["total"] += 1
|
||||||
|
|
||||||
total_elements = 0
|
total_elements = 0
|
||||||
for ifc_class in sorted(results.keys()):
|
for ifc_class in sorted(results.keys()):
|
||||||
@@ -254,17 +255,34 @@ class Spatial(blenderbim.core.tool.Spatial):
|
|||||||
new.name = ifc_class
|
new.name = ifc_class
|
||||||
new.is_class = True
|
new.is_class = True
|
||||||
total = 0
|
total = 0
|
||||||
for type_name in sorted(results[ifc_class].keys()):
|
for ifc_definition_id in sorted(
|
||||||
|
results[ifc_class].keys(), key=lambda x: results[ifc_class][x]["type_name"]
|
||||||
|
):
|
||||||
new2 = props.elements.add()
|
new2 = props.elements.add()
|
||||||
new2.name = type_name
|
|
||||||
new2.is_type = True
|
new2.is_type = True
|
||||||
new2.total = results[ifc_class][type_name]
|
new2.name = results[ifc_class][ifc_definition_id]["type_name"]
|
||||||
|
new2.total = results[ifc_class][ifc_definition_id]["total"]
|
||||||
|
new2.ifc_definition_id = ifc_definition_id
|
||||||
total += new2.total
|
total += new2.total
|
||||||
new.total = total
|
new.total = total
|
||||||
total_elements += total
|
total_elements += total
|
||||||
|
|
||||||
props.total_elements = total_elements
|
props.total_elements = total_elements
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def filter_elements_by_class(cls, elements: List[ifcopenshell.entity_instance], ifc_class: str):
|
||||||
|
return [e for e in elements if e.is_a(ifc_class)]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def filter_elements_by_relating_type(
|
||||||
|
cls, elements: List[ifcopenshell.entity_instance], relating_type: ifcopenshell.entity_instance
|
||||||
|
):
|
||||||
|
return [e for e in elements if ifcopenshell.util.element.get_type(e) == relating_type]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def filter_elements_by_untyped(cls, elements: List[ifcopenshell.entity_instance]):
|
||||||
|
return [e for e in elements if not ifcopenshell.util.element.get_type(e)]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def import_spatial_decomposition(cls) -> None:
|
def import_spatial_decomposition(cls) -> None:
|
||||||
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
props = bpy.context.scene.BIMSpatialDecompositionProperties
|
||||||
|
|||||||
Reference in New Issue
Block a user