From c2e4b5f1fa3ccb00cd0ed086a4ebd140840585b7 Mon Sep 17 00:00:00 2001 From: Cyril Waechter Date: Fri, 19 Nov 2021 00:06:22 +0100 Subject: [PATCH] [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. --- .../bim/module/boundary/__init__.py | 11 ++- .../bim/module/boundary/operator.py | 61 +++++++++++++ .../blenderbim/bim/module/boundary/prop.py | 60 +++++++++++++ .../blenderbim/bim/module/boundary/ui.py | 90 ++++++++++++++++--- .../blenderbim/bim/module/search/operator.py | 13 ++- 5 files changed, 213 insertions(+), 22 deletions(-) create mode 100644 src/blenderbim/blenderbim/bim/module/boundary/prop.py diff --git a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py index 4a18622060..22944a5bfa 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py @@ -17,7 +17,7 @@ # along with BlenderBIM Add-on. If not, see . 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 diff --git a/src/blenderbim/blenderbim/bim/module/boundary/operator.py b/src/blenderbim/blenderbim/bim/module/boundary/operator.py index 4be2cff3e3..b9320937cb 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/operator.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/operator.py @@ -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"} diff --git a/src/blenderbim/blenderbim/bim/module/boundary/prop.py b/src/blenderbim/blenderbim/bim/module/boundary/prop.py new file mode 100644 index 0000000000..e42d414e33 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/boundary/prop.py @@ -0,0 +1,60 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# 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 . + +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) diff --git a/src/blenderbim/blenderbim/bim/module/boundary/ui.py b/src/blenderbim/blenderbim/bim/module/boundary/ui.py index ab8cd0e6bd..56d4444ac2 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/ui.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/ui.py @@ -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") diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index 8b574accef..1b20ba07cb 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -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"}