From 8285e623657b4b752f52cddaa63f79224700ef23 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Thu, 20 Aug 2026 10:34:49 -0500 Subject: [PATCH] Bonsai: add "Share Annotations" option to Duplicate Drawing When duplicating a drawing with "Should Duplicate Annotations" enabled, a new "Share Annotations" toggle lets the manual annotations be shared with the new drawing (multi-group membership - editing one updates both) instead of being copied into independent entities. Auto-generated tags (grid/section/storey) are always copied, since they are regenerated per drawing and cannot be shared. Co-Authored-By: Claude Opus 4.8 --- .../bonsai/bim/module/drawing/operator.py | 16 +++++++-- src/bonsai/bonsai/core/drawing.py | 34 +++++++++++++------ 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index 84a805222f..2b2606c0e0 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -202,6 +202,14 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} drawing: bpy.props.IntProperty() should_duplicate_annotations: bpy.props.BoolProperty(name="Should Duplicate Annotations", default=False) + share_annotations: bpy.props.BoolProperty( + name="Share Annotations", + description=( + "Assign the same annotations to the new drawing (shared - editing one updates both) " + "instead of creating independent copies. Auto-generated tags are always copied" + ), + default=False, + ) @classmethod def poll(cls, context): @@ -216,8 +224,11 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator): def draw(self, context): assert self.layout - row = self.layout - row.prop(self, "should_duplicate_annotations") + layout = self.layout + layout.prop(self, "should_duplicate_annotations") + row = layout.row() + row.enabled = self.should_duplicate_annotations + row.prop(self, "share_annotations") def _execute(self, context): props = tool.Drawing.get_document_props() @@ -228,6 +239,7 @@ class DuplicateDrawing(bpy.types.Operator, tool.Ifc.Operator): tool.Geometry, drawing=tool.Ifc.get().by_id(self.drawing), should_duplicate_annotations=self.should_duplicate_annotations, + share_annotations=self.share_annotations, ) diff --git a/src/bonsai/bonsai/core/drawing.py b/src/bonsai/bonsai/core/drawing.py index 2ac7e99719..7da52fc61a 100644 --- a/src/bonsai/bonsai/core/drawing.py +++ b/src/bonsai/bonsai/core/drawing.py @@ -355,6 +355,7 @@ def duplicate_drawing( geometry: type[tool.Geometry], drawing: ifcopenshell.entity_instance, should_duplicate_annotations: bool = False, + share_annotations: bool = False, ) -> ifcopenshell.entity_instance: drawing_name = drawing_tool.ensure_unique_drawing_name(drawing_tool.get_name(drawing)) new_drawing = ifc.run("root.copy_class", product=drawing) @@ -367,16 +368,29 @@ def duplicate_drawing( ifc.run("group.edit_group", group=new_group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"}) ifc.run("group.assign_group", group=new_group, products=[new_drawing]) if should_duplicate_annotations: - new_annotations: list[ifcopenshell.entity_instance] = [] - annotation_objs = [ifc.get_object(a) for a in drawing_tool.get_group_elements(group) if a != drawing] - old_to_new, _ = geometry.duplicate_ifc_objects(annotation_objs) - for new_elements in old_to_new.values(): - # Remove the Blender object, since we haven't actually activated the duplicated drawing - for new_element in new_elements: - blender.remove_object(ifc.get_object(new_element)) - new_annotations.extend(new_elements) - ifc.run("group.unassign_group", group=group, products=new_annotations) - ifc.run("group.assign_group", group=new_group, products=new_annotations) + source_annotations = [a for a in drawing_tool.get_group_elements(group) if a != drawing] + if share_annotations: + # Share manual annotations with the new drawing (multi-group membership): + # the same entity now belongs to both drawings' groups, so edits propagate. + # Auto-generated tags (grid/section/storey) can't be shared - they are + # regenerated per drawing - so they are still duplicated. + to_share = [a for a in source_annotations if not drawing_tool.is_auto_annotation(a)] + to_duplicate = [a for a in source_annotations if drawing_tool.is_auto_annotation(a)] + if to_share: + ifc.run("group.assign_group", group=new_group, products=to_share) + else: + to_duplicate = source_annotations + if to_duplicate: + new_annotations: list[ifcopenshell.entity_instance] = [] + annotation_objs = [ifc.get_object(a) for a in to_duplicate] + old_to_new, _ = geometry.duplicate_ifc_objects(annotation_objs) + for new_elements in old_to_new.values(): + # Remove the Blender object, since we haven't actually activated the duplicated drawing + for new_element in new_elements: + blender.remove_object(ifc.get_object(new_element)) + new_annotations.extend(new_elements) + ifc.run("group.unassign_group", group=group, products=new_annotations) + ifc.run("group.assign_group", group=new_group, products=new_annotations) old_reference = drawing_tool.get_drawing_document(new_drawing) ifc.run("document.unassign_document", products=[new_drawing], document=old_reference)