From 1b2507e143032391e4ba509454879d1004fcc35c Mon Sep 17 00:00:00 2001 From: Gorgious56 Date: Tue, 19 May 2026 12:29:18 +0200 Subject: [PATCH] Fix 8077 : Fix SHIFT + D with non-ifc object selection When a project has a ifc file associated, selecting non-ifc objects and duplicating them with SHIFT + D now correctly both duplicate them, keep the new objects selected and starts the transform modal. IFC objects behaviour is unaffected. --- .../bonsai/bim/module/geometry/operator.py | 7 ++++- src/bonsai/bonsai/tool/model.py | 4 +-- src/bonsai/test/bim/feature/geometry.feature | 26 +++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index 8d075a9605..e7fa918d50 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -1183,7 +1183,7 @@ class OverrideDuplicateMove(bpy.types.Operator): operator: bpy.types.Operator, context: bpy.types.Context, linked: bool = False ) -> set["rna_enums.OperatorReturnItems"]: # Deep magick from the dawn of time - if tool.Ifc.get(): + if tool.Ifc.get() and tool.Model.has_selected_ifc_objects(include_active=False): IfcStore.execute_ifc_operator(operator, context) return {"FINISHED"} @@ -1287,6 +1287,11 @@ class OverrideDuplicateMove(bpy.types.Operator): if part_obj: all_objects_to_select.add(part_obj) + # Non-IFC duplicates aren't tracked in old_to_new but are left selected by duplicate_ifc_objects + all_objects_to_select.update( + obj for obj in context.selected_objects if not tool.Ifc.get_entity(obj) + ) + # Deselect everything first bpy.ops.object.select_all(action="DESELECT") diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 60f79cc26e..281265cfcc 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -1305,8 +1305,8 @@ class Model(bonsai.core.tool.Model): return [obj for obj in tool.Blender.get_selected_objects() if tool.Ifc.get_entity(obj)] @classmethod - def has_selected_ifc_objects(cls) -> bool: - return any(tool.Ifc.get_entity(obj) for obj in tool.Blender.get_selected_objects()) + def has_selected_ifc_objects(cls, include_active: bool = True) -> bool: + return any(tool.Ifc.get_entity(obj) for obj in tool.Blender.get_selected_objects(include_active=include_active)) @classmethod def get_selected_mesh_objects(cls) -> list[bpy.types.Object]: diff --git a/src/bonsai/test/bim/feature/geometry.feature b/src/bonsai/test/bim/feature/geometry.feature index 3bbf22a5c2..1236f416c8 100644 --- a/src/bonsai/test/bim/feature/geometry.feature +++ b/src/bonsai/test/bim/feature/geometry.feature @@ -285,6 +285,32 @@ Scenario: Override duplicate move - without active IFC data Then the object "Cube" exists And the object "Cube.001" exists +Scenario: Override duplicate move - non-IFC objects inside an IFC project + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + When I duplicate the selected objects + Then the object "Cube" exists + And the object "Cube.001" exists + And the object "Cube.001" is selected + +Scenario: Override duplicate move - mixed IFC and non-IFC selection + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I look at the "Class" panel + And I set the "Products" property to "IfcElement" + And I set the "Class" property to "IfcWall" + And I click "Assign IFC Class" + And I add a cube + And the object "IfcWall/Cube" is selected + And additionally the object "Cube" is selected + When I duplicate the selected objects + Then the object "IfcWall/Cube.001" exists + And the object "IfcWall/Cube.001" is selected + And the object "Cube.001" exists + And the object "Cube.001" is selected + Scenario: Override duplicate move - with active IFC data Given an empty IFC project And I add a cube