From 838e22d27caaf5b3f3bca7d6dbf8398e721a707f Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 30 Jan 2025 17:47:29 +1100 Subject: [PATCH] Fix #6060. Updating long name in spatial manager now also edits attribute for convenience. Editing attributes now also refreshes the spatial manager (i.e. vice versa), and so does bulk attribute copying. --- .../bonsai/bim/module/attribute/operator.py | 47 ++++++++----------- src/bonsai/bonsai/bim/module/attribute/ui.py | 5 +- src/bonsai/bonsai/bim/module/spatial/prop.py | 11 ++++- src/bonsai/bonsai/core/attribute.py | 29 +++++++----- 4 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/attribute/operator.py b/src/bonsai/bonsai/bim/module/attribute/operator.py index b017e222f6..3282c66ec5 100644 --- a/src/bonsai/bonsai/bim/module/attribute/operator.py +++ b/src/bonsai/bonsai/bim/module/attribute/operator.py @@ -19,12 +19,13 @@ import bpy import json import ifcopenshell -import ifcopenshell.api +import ifcopenshell.api.attribute import ifcopenshell.guid import ifcopenshell.util.element import bonsai.bim.helper import bonsai.tool as tool import bonsai.core.attribute as core +import bonsai.core.spatial from bonsai.bim.ifc import IfcStore @@ -112,20 +113,14 @@ class DisableEditingAttributes(bpy.types.Operator): class EditAttributes(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.edit_attributes" bl_label = "Edit Attributes" - bl_description = "ALT + Left Click to edit attributes on all selected objects" + bl_description = "Edit the attributes of the active object" bl_options = {"REGISTER", "UNDO"} - obj: bpy.props.StringProperty(options={"SKIP_SAVE"}) - mass_operation: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) - def invoke(self, context, event): - self.mass_operation = event.alt - return self.execute(context) - - def edit_attributes_on_obj(self, obj): - props = obj.BIMAttributeProperties - product = tool.Ifc.get_entity(obj) - assert product - object_name = tool.Loader.get_name(product) + def _execute(self, context): + self.file = IfcStore.get_file() + obj = tool.Blender.get_active_object(is_selected=False) + if not (element := tool.Ifc.get_entity(obj)): + return def callback(attributes, prop): if prop.name in ("RefLatitude", "RefLongitude"): @@ -136,17 +131,15 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator): attributes[prop.name] = None return True + props = obj.BIMAttributeProperties attributes = bonsai.bim.helper.export_attributes(props.attributes, callback=callback) - ifcopenshell.api.run("attribute.edit_attributes", self.file, product=product, attributes=attributes) + ifcopenshell.api.attribute.edit_attributes(self.file, product=element, attributes=attributes) - tool.Root.set_object_name(obj, product) + tool.Root.set_object_name(obj, element) bpy.ops.bim.disable_editing_attributes(obj=obj.name) - def _execute(self, context): - self.file = IfcStore.get_file() - for obj in get_objs_for_operation(self, context): - self.edit_attributes_on_obj(obj) - return {"FINISHED"} + if tool.Root.is_spatial_element(element): + bonsai.core.spatial.import_spatial_decomposition(tool.Spatial) class GenerateGlobalId(bpy.types.Operator, tool.Ifc.Operator): @@ -195,14 +188,12 @@ class GenerateGlobalId(bpy.types.Operator, tool.Ifc.Operator): class CopyAttributeToSelection(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.copy_attribute_to_selection" bl_label = "Copy Attribute To Selection" - name: bpy.props.StringProperty() bl_options = {"REGISTER", "UNDO"} + name: bpy.props.StringProperty() def _execute(self, context): - value = context.active_object.BIMAttributeProperties.attributes.get(self.name).get_value() - i = 0 - for obj in tool.Blender.get_selected_objects(): - success = core.copy_attribute_to_selection(tool.Ifc, tool.Root, name=self.name, value=value, obj=obj) - if success: - i += 1 - self.report({"INFO"}, f"Attribute was successfully copied to {i} elements.") + value = tool.Blender.get_active_object().BIMAttributeProperties.attributes.get(self.name).get_value() + total = core.copy_attribute_to_selection( + tool.Ifc, tool.Blender, tool.Root, tool.Spatial, name=self.name, value=value + ) + self.report({"INFO"}, f"Attribute was successfully copied to {total} elements.") diff --git a/src/bonsai/bonsai/bim/module/attribute/ui.py b/src/bonsai/bonsai/bim/module/attribute/ui.py index 65529d9d3b..6f0f5a5599 100644 --- a/src/bonsai/bonsai/bim/module/attribute/ui.py +++ b/src/bonsai/bonsai/bim/module/attribute/ui.py @@ -25,13 +25,12 @@ import bonsai.tool as tool def draw_ui(context, layout, attributes): obj = context.active_object - oprops = obj.BIMObjectProperties props = obj.BIMAttributeProperties if props.is_editing_attributes: row = layout.row(align=True) - op = row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes") - op = row.operator("bim.disable_editing_attributes", icon="CANCEL", text="") + row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes") + row.operator("bim.disable_editing_attributes", icon="CANCEL", text="") bonsai.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection") else: diff --git a/src/bonsai/bonsai/bim/module/spatial/prop.py b/src/bonsai/bonsai/bim/module/spatial/prop.py index f296363d68..1017d8f069 100644 --- a/src/bonsai/bonsai/bim/module/spatial/prop.py +++ b/src/bonsai/bonsai/bim/module/spatial/prop.py @@ -65,7 +65,14 @@ def update_name(self: "BIMContainer", context: bpy.types.Context) -> None: element = tool.Ifc.get().by_id(ifc_definition_id) tool.Spatial.edit_container_name(element, self.name) if obj := tool.Ifc.get_object(element): - obj.name = tool.Loader.get_name(element) + tool.Root.set_object_name(obj, element) + bonsai.bim.handler.refresh_ui_data() + + +def update_long_name(self: "BIMContainer", context: bpy.types.Context) -> None: + if ifc_definition_id := self.ifc_definition_id: + element = tool.Ifc.get().by_id(ifc_definition_id) + tool.Ifc.run("attribute.edit_attributes", product=element, attributes={"LongName": self.long_name}) bonsai.bim.handler.refresh_ui_data() @@ -148,7 +155,7 @@ class BIMContainer(PropertyGroup): name: StringProperty(name="Name", update=update_name) ifc_class: StringProperty(name="IFC Class") description: StringProperty(name="Description") - long_name: StringProperty(name="Long Name") + long_name: StringProperty(name="Long Name", update=update_long_name) elevation: StringProperty(name="Elevation", update=update_elevation) level_index: IntProperty(name="Level Index") has_children: BoolProperty(name="Has Children") diff --git a/src/bonsai/bonsai/core/attribute.py b/src/bonsai/bonsai/core/attribute.py index 9ec3809dfb..9d7cafdf1a 100644 --- a/src/bonsai/bonsai/core/attribute.py +++ b/src/bonsai/bonsai/core/attribute.py @@ -26,14 +26,21 @@ if TYPE_CHECKING: def copy_attribute_to_selection( - ifc: tool.Ifc, root: tool.Root, name: str, value: Union[str, None], obj: bpy.types.Object -) -> bool: - if element := ifc.get_entity(obj): - try: - ifc.run("attribute.edit_attributes", product=element, attributes={name: value}) - if name in ("Name", "AxisTag"): - root.set_object_name(obj, element) - return True - except: - pass - return False + ifc: tool.Ifc, blender: tool.Blender, root: tool.Root, spatial: tool.Spatial, name: str, value: Union[str, None] +) -> int: + total_changed = 0 + has_edited_spatial_name = False + for obj in blender.get_selected_objects(include_active=False): + if element := ifc.get_entity(obj): + try: + ifc.run("attribute.edit_attributes", product=element, attributes={name: value}) + if name in ("Name", "AxisTag"): + root.set_object_name(obj, element) + if name in ("Name", "LongName") and root.is_spatial_element(element): + has_edited_spatial_name = True + total_changed += 1 + except: + pass + if has_edited_spatial_name: + spatial.import_spatial_decomposition() + return total_changed