mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 10:06:47 +00:00
Fix #2317 add graphical support for viewing and editing referenced structures
This commit is contained in:
@@ -33,9 +33,9 @@ class SpatialData:
|
||||
def load(cls):
|
||||
cls.data = {
|
||||
"parent_container_id": cls.get_parent_container_id(),
|
||||
"is_contained": cls.is_contained(),
|
||||
"is_directly_contained": cls.is_directly_contained(),
|
||||
"label": cls.get_label(),
|
||||
"label": cls.label(),
|
||||
"references": cls.references(),
|
||||
}
|
||||
cls.is_loaded = True
|
||||
|
||||
@@ -50,11 +50,7 @@ class SpatialData:
|
||||
return container.Decomposes[0].RelatingObject.id()
|
||||
|
||||
@classmethod
|
||||
def is_contained(cls):
|
||||
return ifcopenshell.util.element.get_container(tool.Ifc.get_entity(bpy.context.active_object))
|
||||
|
||||
@classmethod
|
||||
def get_label(cls):
|
||||
def label(cls):
|
||||
container = ifcopenshell.util.element.get_container(tool.Ifc.get_entity(bpy.context.active_object))
|
||||
if container:
|
||||
label = f"{container.is_a()}/{container.Name or ''}"
|
||||
@@ -62,6 +58,11 @@ class SpatialData:
|
||||
label += "*"
|
||||
return label
|
||||
|
||||
@classmethod
|
||||
def references(cls):
|
||||
results = ifcopenshell.util.element.get_referenced_structures(tool.Ifc.get_entity(bpy.context.active_object))
|
||||
return sorted([f"{r.is_a()}/{r.Name or ''}" for r in results])
|
||||
|
||||
@classmethod
|
||||
def is_directly_contained(cls):
|
||||
return bool(getattr(tool.Ifc.get_entity(bpy.context.active_object), "ContainedInStructure", False))
|
||||
|
||||
@@ -27,14 +27,41 @@ from blenderbim.bim.ifc import IfcStore
|
||||
from blenderbim.bim.module.spatial.data import SpatialData
|
||||
|
||||
|
||||
class Operator:
|
||||
def execute(self, context):
|
||||
IfcStore.execute_ifc_operator(self, context)
|
||||
blenderbim.bim.handler.refresh_ui_data()
|
||||
return {"FINISHED"}
|
||||
class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.reference_structure"
|
||||
bl_label = "Reference Structure"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
structure: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
sprops = context.scene.BIMSpatialProperties
|
||||
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
for container in containers:
|
||||
core.reference_structure(tool.Ifc, tool.Spatial, structure=container, element=element)
|
||||
|
||||
|
||||
class AssignContainer(bpy.types.Operator, Operator):
|
||||
class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.dereference_structure"
|
||||
bl_label = "Dereference Structure"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
structure: bpy.props.IntProperty()
|
||||
|
||||
def _execute(self, context):
|
||||
sprops = context.scene.BIMSpatialProperties
|
||||
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
for container in containers:
|
||||
core.dereference_structure(tool.Ifc, tool.Spatial, structure=container, element=element)
|
||||
|
||||
|
||||
class AssignContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.assign_container"
|
||||
bl_label = "Assign Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -48,7 +75,7 @@ class AssignContainer(bpy.types.Operator, Operator):
|
||||
)
|
||||
|
||||
|
||||
class EnableEditingContainer(bpy.types.Operator, Operator):
|
||||
class EnableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.enable_editing_container"
|
||||
bl_label = "Enable Editing Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -57,7 +84,7 @@ class EnableEditingContainer(bpy.types.Operator, Operator):
|
||||
core.enable_editing_container(tool.Spatial, obj=context.active_object)
|
||||
|
||||
|
||||
class ChangeSpatialLevel(bpy.types.Operator, Operator):
|
||||
class ChangeSpatialLevel(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.change_spatial_level"
|
||||
bl_label = "Change Spatial Level"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -67,7 +94,7 @@ class ChangeSpatialLevel(bpy.types.Operator, Operator):
|
||||
core.change_spatial_level(tool.Spatial, parent=tool.Ifc.get().by_id(self.parent))
|
||||
|
||||
|
||||
class DisableEditingContainer(bpy.types.Operator, Operator):
|
||||
class DisableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.disable_editing_container"
|
||||
bl_label = "Disable Editing Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -76,7 +103,7 @@ class DisableEditingContainer(bpy.types.Operator, Operator):
|
||||
core.disable_editing_container(tool.Spatial, obj=context.active_object)
|
||||
|
||||
|
||||
class RemoveContainer(bpy.types.Operator, Operator):
|
||||
class RemoveContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.remove_container"
|
||||
bl_label = "Remove Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -86,11 +113,16 @@ class RemoveContainer(bpy.types.Operator, Operator):
|
||||
core.remove_container(tool.Ifc, tool.Collector, obj=obj)
|
||||
|
||||
|
||||
class CopyToContainer(bpy.types.Operator, Operator):
|
||||
class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
"""
|
||||
Copies selected objects to selected containers
|
||||
Check the mark next to a container in the container list to select it
|
||||
Several containers can be selected at a time
|
||||
Copies selected 3D elements in the viewport to checkmarked spatial containers
|
||||
|
||||
Example: bulk copy a wall to multiple storeys
|
||||
|
||||
1. Select one or more 3D elements in the viewport
|
||||
2. Enable the checkmark next to one or more containers in the container list below to select it
|
||||
3. Press this button
|
||||
4. The copied elements will have a new position relative to the destination containers
|
||||
"""
|
||||
|
||||
bl_idname = "bim.copy_to_container"
|
||||
@@ -105,7 +137,7 @@ class CopyToContainer(bpy.types.Operator, Operator):
|
||||
blenderbim.bim.handler.purge_module_data()
|
||||
|
||||
|
||||
class SelectContainer(bpy.types.Operator, Operator):
|
||||
class SelectContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_container"
|
||||
bl_label = "Select Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -114,7 +146,7 @@ class SelectContainer(bpy.types.Operator, Operator):
|
||||
core.select_container(tool.Ifc, tool.Spatial, obj=context.active_object)
|
||||
|
||||
|
||||
class SelectSimilarContainer(bpy.types.Operator, Operator):
|
||||
class SelectSimilarContainer(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.select_similar_container"
|
||||
bl_label = "Select Similar Container"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@@ -55,13 +55,15 @@ class BIM_PT_spatial(Panel):
|
||||
if props.containers and props.active_container_index < len(props.containers):
|
||||
op = row.operator("bim.assign_container", icon="CHECKMARK")
|
||||
op.structure = props.containers[props.active_container_index].ifc_definition_id
|
||||
row.operator("bim.reference_structure", icon="LINKED", text="")
|
||||
row.operator("bim.dereference_structure", icon="UNLINKED", text="")
|
||||
row.operator("bim.copy_to_container", icon="COPYDOWN", text="")
|
||||
row.operator("bim.disable_editing_container", icon="CANCEL", text="")
|
||||
|
||||
self.layout.template_list("BIM_UL_containers", "", props, "containers", props, "active_container_index")
|
||||
else:
|
||||
row = self.layout.row(align=True)
|
||||
if SpatialData.data["is_contained"]:
|
||||
if SpatialData.data["label"]:
|
||||
row.label(text=SpatialData.data["label"])
|
||||
row.operator("bim.select_container", icon="TRACKER", text="")
|
||||
row.operator("bim.select_similar_container", icon="RESTRICT_SELECT_OFF", text="")
|
||||
@@ -71,6 +73,9 @@ class BIM_PT_spatial(Panel):
|
||||
else:
|
||||
row.label(text="This object is not spatially contained")
|
||||
row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="")
|
||||
for reference in SpatialData.data["references"]:
|
||||
row = self.layout.row()
|
||||
row.label(text=reference, icon="LINKED")
|
||||
|
||||
|
||||
class BIM_UL_containers(UIList):
|
||||
|
||||
@@ -19,6 +19,16 @@
|
||||
import blenderbim.core
|
||||
|
||||
|
||||
def reference_structure(ifc, spatial, structure=None, element=None):
|
||||
if spatial.can_reference(structure, element):
|
||||
return ifc.run("spatial.reference_structure", product=element, relating_structure=structure)
|
||||
|
||||
|
||||
def dereference_structure(ifc, spatial, structure=None, element=None):
|
||||
if spatial.can_reference(structure, element):
|
||||
return ifc.run("spatial.dereference_structure", product=element, relating_structure=structure)
|
||||
|
||||
|
||||
def assign_container(ifc, collector, spatial, structure_obj=None, element_obj=None):
|
||||
if not spatial.can_contain(structure_obj, element_obj):
|
||||
return
|
||||
|
||||
@@ -435,6 +435,7 @@ class Sequence:
|
||||
@interface
|
||||
class Spatial:
|
||||
def can_contain(cls, structure_obj, element_obj): pass
|
||||
def can_reference(cls, structure, element): pass
|
||||
def disable_editing(cls, obj): pass
|
||||
def duplicate_object_and_data(cls, obj): pass
|
||||
def enable_editing(cls, obj): pass
|
||||
|
||||
@@ -41,6 +41,20 @@ class Spatial(blenderbim.core.tool.Spatial):
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def can_reference(cls, structure, element):
|
||||
if not structure or not element:
|
||||
return False
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
if not structure.is_a("IfcSpatialStructureElement"):
|
||||
return False
|
||||
else:
|
||||
if not structure.is_a("IfcSpatialElement"):
|
||||
return False
|
||||
if not hasattr(element, "ReferencedInStructures"):
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def disable_editing(cls, obj):
|
||||
obj.BIMObjectSpatialProperties.is_editing = False
|
||||
|
||||
@@ -41,6 +41,31 @@ Scenario: Copy to container
|
||||
And I press "bim.copy_to_container"
|
||||
Then the object "IfcWall/Cube.001" is in the collection "IfcSite/My Site"
|
||||
|
||||
Scenario: Reference structure
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.enable_editing_container"
|
||||
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
|
||||
And I press "bim.reference_structure"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Dereference structure
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the object "IfcWall/Cube" is selected
|
||||
And I press "bim.enable_editing_container"
|
||||
When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True"
|
||||
And I press "bim.reference_structure"
|
||||
And I press "bim.dereference_structure"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Select container
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
|
||||
@@ -20,6 +20,20 @@ import blenderbim.core.spatial as subject
|
||||
from test.core.bootstrap import ifc, collector, spatial
|
||||
|
||||
|
||||
class TestReferenceStructure:
|
||||
def test_run(self, ifc, spatial):
|
||||
spatial.can_reference("structure", "element").should_be_called().will_return(True)
|
||||
ifc.run("spatial.reference_structure", product="element", relating_structure="structure").should_be_called()
|
||||
subject.reference_structure(ifc, spatial, structure="structure", element="element")
|
||||
|
||||
|
||||
class TestDereferenceStructure:
|
||||
def test_run(self, ifc, spatial):
|
||||
spatial.can_reference("structure", "element").should_be_called().will_return(True)
|
||||
ifc.run("spatial.dereference_structure", product="element", relating_structure="structure").should_be_called()
|
||||
subject.dereference_structure(ifc, spatial, structure="structure", element="element")
|
||||
|
||||
|
||||
class TestAssignContainer:
|
||||
def test_run(self, ifc, collector, spatial):
|
||||
spatial.can_contain("structure_obj", "element_obj").should_be_called().will_return(True)
|
||||
|
||||
@@ -92,6 +92,25 @@ class TestCanContain(NewFile):
|
||||
assert subject.can_contain(structure_obj, element_obj) is True
|
||||
|
||||
|
||||
class TestCanReference(NewFile):
|
||||
def test_an_element_can_reference_a_spatial_element(self):
|
||||
ifc = ifcopenshell.file()
|
||||
assert subject.can_reference(ifc.createIfcSite(), ifc.createIfcWall()) is True
|
||||
|
||||
def test_an_element_can_reference_a_spatial_element_ifc2x3(self):
|
||||
ifc = ifcopenshell.file(schema="IFC2X3")
|
||||
tool.Ifc.set(ifc)
|
||||
assert subject.can_reference(ifc.createIfcSite(), ifc.createIfcWall()) is True
|
||||
|
||||
def test_a_non_spatial_element_cannot_reference_anything(self):
|
||||
ifc = ifcopenshell.file()
|
||||
assert subject.can_reference(ifc.createIfcWall(), ifc.createIfcWall()) is False
|
||||
|
||||
def test_a_non_element_cannot_reference_anything(self):
|
||||
ifc = ifcopenshell.file()
|
||||
assert subject.can_reference(ifc.createIfcSite(), ifc.createIfcTask()) is False
|
||||
|
||||
|
||||
class TestDisableEditing(NewFile):
|
||||
def test_run(self):
|
||||
obj = bpy.data.objects.new("Object", None)
|
||||
|
||||
Reference in New Issue
Block a user