Fix #2317 add graphical support for viewing and editing referenced structures

This commit is contained in:
Dion Moult
2022-08-22 13:52:12 +10:00
parent 0844d6f52e
commit 9821c3af67
9 changed files with 145 additions and 24 deletions
@@ -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):
+10
View File
@@ -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
+1
View File
@@ -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
+14
View File
@@ -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
+14
View File
@@ -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)
+19
View File
@@ -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)