diff --git a/src/bonsai/bonsai/bim/module/geometry/__init__.py b/src/bonsai/bonsai/bim/module/geometry/__init__.py index 111b67b5b05..4b8a8abb29e 100644 --- a/src/bonsai/bonsai/bim/module/geometry/__init__.py +++ b/src/bonsai/bonsai/bim/module/geometry/__init__.py @@ -69,6 +69,7 @@ operator.RemoveRepresentation, operator.RemoveRepresentationItem, operator.RemoveRepresentationItemFromShapeAspect, + operator.SelectByRepresentationType, operator.SelectConnection, operator.SelectRepresentationItem, operator.SwitchRepresentation, diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index 7382b093f4b..96a7bb7ccf9 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -417,6 +417,55 @@ def _execute(self, context): core.select_connection(tool.Geometry, connection=tool.Ifc.get().by_id(self.connection)) +class SelectByRepresentationType(bpy.types.Operator): + bl_idname = "bim.select_by_representation_type" + bl_label = "Select By Representation Type" + bl_description = ( + "Select objects whose active representation matches this type. " + "Ctrl+Click to also include objects that have this type in any representation (active or not)" + ) + bl_options = {"REGISTER", "UNDO"} + representation_type: bpy.props.StringProperty() + select_inactive: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + + def invoke(self, context, event): + self.select_inactive = event.ctrl + return self.execute(context) + + def execute(self, context): + ifc = tool.Ifc.get() + if not ifc: + return {"CANCELLED"} + # Strip the "*" suffix used for mapped/resolved representations. + target_type = self.representation_type.rstrip("*") + matched = 0 + for obj in context.visible_objects: + element = tool.Ifc.get_entity(obj) + if not element: + obj.select_set(False) + continue + if self.select_inactive: + # Ctrl: match any representation on the element, active or not. + has_type = any( + (ifcopenshell.util.representation.resolve_representation(rep).RepresentationType or "") == target_type + for rep in ifcopenshell.util.representation.get_representations_iter(element) + ) + else: + # Default: match only the currently active (displayed) representation. + active_rep = tool.Geometry.get_active_representation(obj) + if active_rep is None: + obj.select_set(False) + continue + resolved = ifcopenshell.util.representation.resolve_representation(active_rep) + has_type = (resolved.RepresentationType or "") == target_type + obj.select_set(has_type) + if has_type: + matched += 1 + mode = "any representation" if self.select_inactive else "active representation" + self.report({"INFO"}, f"Selected {matched} object(s) with RepresentationType '{target_type}' ({mode})") + return {"FINISHED"} + + class RemoveConnection(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_connection" bl_label = "Remove Connection" diff --git a/src/bonsai/bonsai/bim/module/geometry/ui.py b/src/bonsai/bonsai/bim/module/geometry/ui.py index e7912c7a779..bf391c61693 100644 --- a/src/bonsai/bonsai/bim/module/geometry/ui.py +++ b/src/bonsai/bonsai/bim/module/geometry/ui.py @@ -152,7 +152,12 @@ def draw(self, context): row.label(text=representation["ContextType"]) row.label(text=representation["ContextIdentifier"]) row.label(text=representation["TargetView"]) - row.label(text=representation["RepresentationType"]) + op = row.operator( + "bim.select_by_representation_type", + text=representation["RepresentationType"], + emboss=False, + ) + op.representation_type = representation["RepresentationType"] op = row.operator( "bim.switch_representation", icon="FILE_REFRESH" if representation["is_active"] else "OUTLINER_DATA_MESH",