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.
This commit is contained in:
Dion Moult
2025-01-30 17:47:29 +11:00
parent 8f78fe56de
commit 838e22d27c
4 changed files with 48 additions and 44 deletions
@@ -19,12 +19,13 @@
import bpy import bpy
import json import json
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api.attribute
import ifcopenshell.guid import ifcopenshell.guid
import ifcopenshell.util.element import ifcopenshell.util.element
import bonsai.bim.helper import bonsai.bim.helper
import bonsai.tool as tool import bonsai.tool as tool
import bonsai.core.attribute as core import bonsai.core.attribute as core
import bonsai.core.spatial
from bonsai.bim.ifc import IfcStore from bonsai.bim.ifc import IfcStore
@@ -112,20 +113,14 @@ class DisableEditingAttributes(bpy.types.Operator):
class EditAttributes(bpy.types.Operator, tool.Ifc.Operator): class EditAttributes(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.edit_attributes" bl_idname = "bim.edit_attributes"
bl_label = "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"} 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): def _execute(self, context):
self.mass_operation = event.alt self.file = IfcStore.get_file()
return self.execute(context) obj = tool.Blender.get_active_object(is_selected=False)
if not (element := tool.Ifc.get_entity(obj)):
def edit_attributes_on_obj(self, obj): return
props = obj.BIMAttributeProperties
product = tool.Ifc.get_entity(obj)
assert product
object_name = tool.Loader.get_name(product)
def callback(attributes, prop): def callback(attributes, prop):
if prop.name in ("RefLatitude", "RefLongitude"): if prop.name in ("RefLatitude", "RefLongitude"):
@@ -136,17 +131,15 @@ class EditAttributes(bpy.types.Operator, tool.Ifc.Operator):
attributes[prop.name] = None attributes[prop.name] = None
return True return True
props = obj.BIMAttributeProperties
attributes = bonsai.bim.helper.export_attributes(props.attributes, callback=callback) 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) bpy.ops.bim.disable_editing_attributes(obj=obj.name)
def _execute(self, context): if tool.Root.is_spatial_element(element):
self.file = IfcStore.get_file() bonsai.core.spatial.import_spatial_decomposition(tool.Spatial)
for obj in get_objs_for_operation(self, context):
self.edit_attributes_on_obj(obj)
return {"FINISHED"}
class GenerateGlobalId(bpy.types.Operator, tool.Ifc.Operator): 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): class CopyAttributeToSelection(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.copy_attribute_to_selection" bl_idname = "bim.copy_attribute_to_selection"
bl_label = "Copy Attribute To Selection" bl_label = "Copy Attribute To Selection"
name: bpy.props.StringProperty()
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
name: bpy.props.StringProperty()
def _execute(self, context): def _execute(self, context):
value = context.active_object.BIMAttributeProperties.attributes.get(self.name).get_value() value = tool.Blender.get_active_object().BIMAttributeProperties.attributes.get(self.name).get_value()
i = 0 total = core.copy_attribute_to_selection(
for obj in tool.Blender.get_selected_objects(): tool.Ifc, tool.Blender, tool.Root, tool.Spatial, name=self.name, value=value
success = core.copy_attribute_to_selection(tool.Ifc, tool.Root, name=self.name, value=value, obj=obj) )
if success: self.report({"INFO"}, f"Attribute was successfully copied to {total} elements.")
i += 1
self.report({"INFO"}, f"Attribute was successfully copied to {i} elements.")
+2 -3
View File
@@ -25,13 +25,12 @@ import bonsai.tool as tool
def draw_ui(context, layout, attributes): def draw_ui(context, layout, attributes):
obj = context.active_object obj = context.active_object
oprops = obj.BIMObjectProperties
props = obj.BIMAttributeProperties props = obj.BIMAttributeProperties
if props.is_editing_attributes: if props.is_editing_attributes:
row = layout.row(align=True) row = layout.row(align=True)
op = row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes") row.operator("bim.edit_attributes", icon="CHECKMARK", text="Save Attributes")
op = row.operator("bim.disable_editing_attributes", icon="CANCEL", text="") row.operator("bim.disable_editing_attributes", icon="CANCEL", text="")
bonsai.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection") bonsai.bim.helper.draw_attributes(props.attributes, layout, copy_operator="bim.copy_attribute_to_selection")
else: else:
+9 -2
View File
@@ -65,7 +65,14 @@ def update_name(self: "BIMContainer", context: bpy.types.Context) -> None:
element = tool.Ifc.get().by_id(ifc_definition_id) element = tool.Ifc.get().by_id(ifc_definition_id)
tool.Spatial.edit_container_name(element, self.name) tool.Spatial.edit_container_name(element, self.name)
if obj := tool.Ifc.get_object(element): 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() bonsai.bim.handler.refresh_ui_data()
@@ -148,7 +155,7 @@ class BIMContainer(PropertyGroup):
name: StringProperty(name="Name", update=update_name) name: StringProperty(name="Name", update=update_name)
ifc_class: StringProperty(name="IFC Class") ifc_class: StringProperty(name="IFC Class")
description: StringProperty(name="Description") 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) elevation: StringProperty(name="Elevation", update=update_elevation)
level_index: IntProperty(name="Level Index") level_index: IntProperty(name="Level Index")
has_children: BoolProperty(name="Has Children") has_children: BoolProperty(name="Has Children")
+18 -11
View File
@@ -26,14 +26,21 @@ if TYPE_CHECKING:
def copy_attribute_to_selection( def copy_attribute_to_selection(
ifc: tool.Ifc, root: tool.Root, name: str, value: Union[str, None], obj: bpy.types.Object ifc: tool.Ifc, blender: tool.Blender, root: tool.Root, spatial: tool.Spatial, name: str, value: Union[str, None]
) -> bool: ) -> int:
if element := ifc.get_entity(obj): total_changed = 0
try: has_edited_spatial_name = False
ifc.run("attribute.edit_attributes", product=element, attributes={name: value}) for obj in blender.get_selected_objects(include_active=False):
if name in ("Name", "AxisTag"): if element := ifc.get_entity(obj):
root.set_object_name(obj, element) try:
return True ifc.run("attribute.edit_attributes", product=element, attributes={name: value})
except: if name in ("Name", "AxisTag"):
pass root.set_object_name(obj, element)
return False 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