[BlenderBIM] Boundaries: add relations editing (#1881)

* SelectGlobalId: set object as active

Use tool module instead of previous custom search loop.

* Boundaries: add relations editing

For entity of class IfcRelSpaceBoundary :
* Display relationship attributes
* Add button to jump to related objects
* Allow to edit relations attributes including entity filter.
UI similar to current base attributes editing.
This commit is contained in:
Cyril Waechter
2021-11-19 00:06:22 +01:00
committed by GitHub
parent 9728c4d345
commit c2e4b5f1fa
5 changed files with 213 additions and 22 deletions
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import ui, operator
from . import ui, operator, prop
classes = (
operator.LoadProjectSpaceBoundaries,
@@ -25,14 +25,19 @@ classes = (
operator.LoadBoundary,
operator.SelectProjectBoundaries,
operator.ColourByRelatedBuildingElement,
operator.EnableEditingBoundary,
operator.DisableEditingBoundary,
operator.EditBoundaryAttributes,
ui.BIM_PT_Boundary,
ui.BIM_PT_SpaceBoundaries,
ui.BIM_PT_SceneBoundaries,
prop.BIMBoundaryProperties,
)
def register():
pass
bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties)
def unregister():
pass
del bpy.types.Object.bim_boundary_properties
@@ -202,3 +202,64 @@ class ColourByRelatedBuildingElement(bpy.types.Operator):
def commit(self, data):
if data:
data["area"].spaces[0].shading.color_type = "OBJECT"
EDITABLE_ATTRIBUTES = {
"RelatingSpace": "relating_space",
"RelatedBuildingElement": "related_building_element",
"ParentBoundary": "parent_boundary",
"CorrespondingBoundary": "corresponding_boundary",
}
class EnableEditingBoundary(bpy.types.Operator):
bl_idname = "bim.enable_editing_boundary"
bl_label = "Edit boundary relations"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bprops = context.active_object.bim_boundary_properties
bprops.is_editing = True
boundary = tool.Ifc.get_entity(context.active_object)
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
entity = getattr(boundary, ifc_attribute, None)
if not entity:
continue
obj = tool.Ifc.get_object(entity)
if entity and obj:
setattr(bprops, blender_property, obj)
return {"FINISHED"}
class DisableEditingBoundary(bpy.types.Operator):
bl_idname = "bim.disable_editing_boundary"
bl_label = "Disable editing boundary relations"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
bprops = context.active_object.bim_boundary_properties
bprops.is_editing = False
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
setattr(bprops, blender_property, None)
return {"FINISHED"}
class EditBoundaryAttributes(bpy.types.Operator):
bl_idname = "bim.edit_boundary_attributes"
bl_label = "Disable editing boundary relations"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context):
bprops = context.active_object.bim_boundary_properties
boundary = tool.Ifc.get_entity(context.active_object)
for ifc_attribute, blender_property in EDITABLE_ATTRIBUTES.items():
if not hasattr(boundary, ifc_attribute):
continue
obj = getattr(bprops, blender_property, None)
entity = tool.Ifc.get_entity(obj)
setattr(boundary, ifc_attribute, entity)
bpy.ops.bim.disable_editing_boundary()
return {"FINISHED"}
@@ -0,0 +1,60 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
import blenderbim.tool as tool
def space_filter(self, object):
entity = tool.Ifc.get_entity(object)
if entity:
return entity.is_a("IfcSpace") or entity.is_a("IfcExternalSpatialElement")
return False
def boundary_filter(self, object):
entity = tool.Ifc.get_entity(object)
if entity:
return entity.is_a("IfcRelSpaceBoundary")
return False
def element_filter(self, object):
entity = tool.Ifc.get_entity(object)
if entity:
return entity.is_a("IfcElement")
return False
class BIMBoundaryProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter)
related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter)
parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter)
corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter)
@@ -37,15 +37,80 @@ class BIM_PT_SceneBoundaries(Panel):
return IfcStore.get_file()
def draw(self, context):
row = self.layout.row()
row = self.layout.row(align=True)
row.operator("bim.load_project_space_boundaries")
row.operator("bim.select_project_space_boundaries", text="", icon="RESTRICT_SELECT_OFF")
row.operator("bim.colour_by_related_building_element", text="", icon="BRUSH_DATA")
class BIM_PT_Boundary(Panel):
bl_label = "IFC Space Boundaries"
bl_label = "IFC Space Boundary"
bl_idname = "BIM_PT_Boundary"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
@classmethod
def poll(cls, context):
if not context.active_object:
return False
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
return entity.is_a("IfcRelSpaceBoundary")
def draw(self, context):
props = context.active_object.BIMObjectProperties
ifc_file = tool.Ifc.get()
element = ifc_file.by_id(props.ifc_definition_id)
boundary = ifc_file.by_id(props.ifc_definition_id)
self.bprops = context.active_object.bim_boundary_properties
if self.bprops.is_editing:
row = self.layout.row(align=True)
row.operator("bim.edit_boundary_attributes", icon="CHECKMARK", text="Save Attributes")
row.operator("bim.disable_editing_boundary", icon="CANCEL", text="")
self.draw_relation_editor(boundary, "RelatingSpace", "relating_space")
self.draw_relation_editor(boundary, "RelatedBuildingElement", "related_building_element")
self.draw_relation_editor(boundary, "ParentBoundary", "parent_boundary")
self.draw_relation_editor(boundary, "CorrespondingBoundary", "corresponding_boundary")
else:
row = self.layout.row()
row.operator("bim.enable_editing_boundary", icon="GREASEPENCIL", text="Edit")
self.draw_relation_data(boundary, "RelatingSpace")
self.draw_relation_data(boundary, "RelatedBuildingElement")
self.draw_relation_data(boundary, "ParentBoundary")
self.draw_relation_data(boundary, "CorrespondingBoundary")
if hasattr(boundary, "InnerBoundaries"):
for i, inner_boundary in enumerate(getattr(boundary, "InnerBoundaries", ())):
row = self.layout.row(align=True)
row.label(text="InnerBoundaries")
row.label(text=f"[{i}]")
row.label(text=f"{inner_boundary.is_a()}/{inner_boundary.Name}")
def draw_relation_data(self, boundary, ifc_attribute: str):
row = self.layout.row(align=True)
if hasattr(boundary, ifc_attribute):
row.label(text=ifc_attribute)
entity = getattr(boundary, ifc_attribute)
if entity:
row.label(text=f"{entity.is_a()}/{entity.Name}")
op = row.operator("bim.select_global_id", text="", icon="TRACKER")
op.global_id = entity.GlobalId
else:
row.label(text="")
def draw_relation_editor(self, boundary, ifc_attribute: str, blender_property: str):
if hasattr(boundary, ifc_attribute):
row = self.layout.row(align=True)
row.prop(self.bprops, blender_property)
class BIM_PT_SpaceBoundaries(Panel):
bl_label = "IFC Space Boundaries"
bl_idname = "BIM_PT_SpaceBoundaries"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
@@ -60,20 +125,23 @@ class BIM_PT_Boundary(Panel):
return False
if not IfcStore.get_element(props.ifc_definition_id):
return False
if IfcStore.get_file().by_id(props.ifc_definition_id).is_a() not in ["IfcSpace", "IfcExternalSpatialElement"]:
return False
return True
valid_classes = ("IfcSpace", "IfcExternalSpatialElement")
entity = IfcStore.get_file().by_id(props.ifc_definition_id)
for ifc_class in valid_classes:
if entity.is_a(ifc_class):
return True
return False
def draw(self, context):
self.oprops = context.active_object.BIMObjectProperties
self.props = context.active_object.BIMObjectProperties
self.ifc_file = tool.Ifc.get()
row = self.layout.row()
row.operator("bim.load_space_boundaries")
ifc_file = tool.Ifc.get()
if not Data.is_loaded:
Data.load(ifc_file)
for boundary_id in Data.spaces.get(self.oprops.ifc_definition_id, []):
boundary = Data.boundaries[boundary_id]
building_element = ifc_file.by_id(boundary["RelatedBuildingElement"])
Data.load(self.ifc_file)
for boundary_id in Data.spaces.get(self.props.ifc_definition_id, []):
boundary_data = Data.boundaries[boundary_id]
building_element = self.ifc_file.by_id(boundary_data["RelatedBuildingElement"])
row = self.layout.row()
row.label(text=f"{boundary_id} > {building_element.is_a()}/{building_element.Name}", icon="GHOST_ENABLED")
op = row.operator("bim.load_boundary", text="", icon="RESTRICT_SELECT_OFF")
@@ -63,16 +63,13 @@ class SelectGlobalId(bpy.types.Operator):
global_id: bpy.props.StringProperty()
def execute(self, context):
self.file = IfcStore.get_file()
ifc_file = tool.Ifc.get()
props = context.scene.BIMSearchProperties
global_id = self.global_id or props.global_id
for obj in context.visible_objects:
if not obj.BIMObjectProperties.ifc_definition_id:
continue
element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
if element.GlobalId == global_id:
obj.select_set(True)
break
entity = ifc_file.by_guid(global_id)
obj = tool.Ifc.get_object(entity)
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
return {"FINISHED"}