Compare commits

...

11 Commits

Author SHA1 Message Date
Ryan Schultz 60ccd3ab7e Add ALT+Click unhide to select_group_elements
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:16:04 -05:00
Ryan Schultz f0d8a6f4f4 Add ALT+Click unhide to select_aggregate
When ALT is held, BIM_OT_select_aggregate now clears
hide_viewport and hide_set on matched objects before
selecting, consistent with the pattern used across other
selection operators on this branch.

Generated with the assistance of an AI coding tool.
2026-04-19 06:49:12 -05:00
Ryan Schultz 2ad30a00d7 Add container tools to spatial decomposition panel
- Add bim.select_similar_container button to the spatial
  decomposition panel, gated behind the new
  show_container_tools preference (renamed from
  container_hide_show_isolate)
- Fix select_similar_container to resolve container from
  the active list item rather than the active object
- Unhide individual objects when showing container
  visibility (set_container_visibility SHOW/ISOLATE)

Generated with the assistance of an AI coding tool.
2026-03-20 20:06:53 -05:00
Ryan Schultz e3d5e1d1fe Unhide objects when showing container visibility
When mode is SHOW or ISOLATE, also unhide individual
objects (hide_viewport and hide_set) within each
container, not just the collection-level visibility.

Generated with the assistance of an AI coding tool.
2026-03-20 19:38:57 -05:00
Ryan Schultz 15228ac8b5 Add ALT+Click unhide to select_decomposed_elements
Moves "select all listed elements" from ALT to SHIFT,
freeing ALT+Click to unhide hidden objects (viewport
and local hide) before selecting.

Generated with the assistance of an AI coding tool.
2026-03-20 19:20:43 -05:00
Ryan Schultz 011fb6660a Add ALT+Click unhide to select_linked_aggregates
ALT+Click now unhides hidden objects (viewport and local
hide) before selecting, matching the behavior added to
other select operators.

Generated with the assistance of an AI coding tool.
2026-03-20 19:06:04 -05:00
Ryan Schultz cd458780e6 Add ALT+Click unhide to select_by_material
ALT+Click now unhides hidden objects (viewport and local
hide) before selecting, matching the behavior added to
other select operators.

Generated with the assistance of an AI coding tool.
2026-03-20 19:00:47 -05:00
Ryan Schultz 873f9f061f Add ALT+Click unhide to select_similar_container
ALT+Click now unhides hidden objects (viewport and local
hide) before selecting. Also fixes select_products to set
hide_viewport in addition to hide_set when unhiding.

Generated with the assistance of an AI coding tool.
2026-03-20 18:54:43 -05:00
Ryan Schultz ea27b4f3ae Add ALT+Click unhide to select_similar_type
ALT+Click now unhides hidden objects (viewport and local
hide) before selecting, matching the behavior added to
select_ifc_class and select_similar.

Generated with the assistance of an AI coding tool.
2026-03-20 12:33:51 -05:00
Ryan Schultz 1ce62c948c Add ALT+Click unhide to select_similar
ALT+Click now unhides hidden objects (viewport and local
hide) before selecting, matching the same behavior added
to select_ifc_class.

Generated with the assistance of an AI coding tool.
2026-03-20 12:30:11 -05:00
Ryan Schultz f327a999d0 Closes #7780 - Add ALT+Click unhide to select_ifc_class
Clear hide_viewport and hide_set on matched objects
when the operator is invoked with Alt held, so hidden
objects of the target IFC class are revealed and selected.

Generated with the assistance of an AI coding tool.
2026-03-20 12:20:15 -05:00
11 changed files with 111 additions and 34 deletions
@@ -266,17 +266,19 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
one_level_deep: bpy.props.BoolProperty(
name="One Level Deep", description="Select only immediate children, not recursively", default=False
)
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
@classmethod
def description(cls, context, properties):
if properties.select_parts:
return "Select Aggregate and Parts.\n\nCtrl+click to select only one level deep"
return "Select Aggregate and Parts.\n\nCtrl+click to select only one level deep\nALT+Click to also unhide hidden objects (viewport and local hide)"
else:
return "Select Aggregate"
return "Select Aggregate\n\nALT+Click to also unhide hidden objects (viewport and local hide)"
def invoke(self, context, event):
if event.type == "LEFTMOUSE" and event.ctrl:
self.one_level_deep = True
self.should_unhide = event.alt
return self.execute(context)
def execute(self, context):
@@ -317,12 +319,18 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
for element in set(selected_parts + all_parts):
obj = tool.Ifc.get_object(element)
if obj:
if self.should_unhide:
obj.hide_viewport = False
obj.hide_set(False)
obj.select_set(True)
else:
for aggregate_element in all_parts:
aggregate_obj = tool.Ifc.get_object(aggregate_element)
if aggregate_obj:
if self.should_unhide:
aggregate_obj.hide_viewport = False
aggregate_obj.hide_set(False)
aggregate_obj.select_set(True)
bpy.context.view_layer.objects.active = aggregate_obj
@@ -407,13 +415,18 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator):
bl_label = "Select linked aggregates"
bl_options = {"REGISTER", "UNDO"}
select_parts: bpy.props.BoolProperty(default=False)
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
@classmethod
def description(cls, context, properties):
if properties.select_parts:
return "Select all aggregates, subaggregates and all their parts"
return "Select all aggregates, subaggregates and all their parts\n\nALT+Click to also unhide hidden objects (viewport and local hide)"
else:
return "Select all aggregates"
return "Select all aggregates\n\nALT+Click to also unhide hidden objects (viewport and local hide)"
def invoke(self, context, event):
self.should_unhide = event.alt
return self.execute(context)
def execute(self, context):
for obj in context.selected_objects:
@@ -446,11 +459,18 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator):
for element in parts_objs:
obj = tool.Ifc.get_object(element)
if obj:
if self.should_unhide:
obj.hide_viewport = False
obj.hide_set(False)
obj.select_set(True)
else:
for element in parts:
obj = tool.Ifc.get_object(element)
obj.select_set(True)
if obj:
if self.should_unhide:
obj.hide_viewport = False
obj.hide_set(False)
obj.select_set(True)
return {"FINISHED"}
@@ -196,18 +196,23 @@ class SelectGroupElements(bpy.types.Operator):
bl_label = "Select Group elements"
bl_options = {"REGISTER", "UNDO"}
bl_description = (
"Select objects assigned to the selected group and all nested groups\nALT + CLICK to exclude children"
"Select objects assigned to the selected group and all nested groups"
"\nCTRL + CLICK to exclude children"
"\nALT + CLICK to also unhide hidden objects (viewport and local hide)"
)
group: bpy.props.IntProperty()
is_recursive: bpy.props.BoolProperty(name="Is Recursive", default=True, options={"SKIP_SAVE"})
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
self.is_recursive = not event.alt
self.is_recursive = not event.ctrl
self.should_unhide = event.alt
return self.execute(context)
def execute(self, context):
tool.Spatial.select_products(
ifcopenshell.util.element.get_grouped_by(tool.Ifc.get().by_id(self.group), is_recursive=self.is_recursive)
ifcopenshell.util.element.get_grouped_by(tool.Ifc.get().by_id(self.group), is_recursive=self.is_recursive),
unhide=self.should_unhide,
)
return {"FINISHED"}
@@ -61,13 +61,18 @@ class DisableEditingMaterials(bpy.types.Operator):
class SelectByMaterial(bpy.types.Operator):
bl_idname = "bim.select_by_material"
bl_label = "Select By Material"
bl_description = "Select objects using the provided material"
bl_description = "Select objects using the provided material\n\nALT+Click to also unhide hidden objects (viewport and local hide)"
bl_options = {"REGISTER", "UNDO"}
material: bpy.props.IntProperty()
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
self.should_unhide = event.alt
return self.execute(context)
def execute(self, context):
material = tool.Ifc.get().by_id(self.material)
core.select_by_material(tool.Material, tool.Spatial, material=material)
core.select_by_material(tool.Material, tool.Spatial, material=material, should_unhide=self.should_unhide)
# copy selection query to clipboard
if material.is_a("IfcMaterialLayerSet"):
@@ -1264,15 +1264,17 @@ class SelectGlobalId(Operator):
class SelectIfcClass(Operator):
"""Click to select all objects that match with the given IFC class\nSHIFT + Click to also match Predefined Type"""
"""Click to select all objects that match with the given IFC class\nSHIFT + Click to also match Predefined Type\nALT + Click to also unhide hidden objects (viewport and local hide)"""
bl_idname = "bim.select_ifc_class"
bl_label = "Select IFC Class"
bl_options = {"REGISTER", "UNDO"}
should_filter_predefined_type: BoolProperty(default=False)
should_unhide: BoolProperty(default=False)
def invoke(self, context, event):
self.should_filter_predefined_type = event.shift
self.should_unhide = event.alt
return self.execute(context)
def execute(self, context):
@@ -1292,6 +1294,9 @@ class SelectIfcClass(Operator):
):
continue
if obj := tool.Ifc.get_object(element):
if self.should_unhide:
obj.hide_viewport = False
obj.hide_set(False)
tool.Blender.select_object(obj)
# copy selection query to clipboard
@@ -1451,10 +1456,11 @@ class SelectSimilar(Operator):
)
calculated_sum: bpy.props.FloatProperty(name="Calculated Sum", default=0.0)
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
@classmethod
def description(cls, context, properties):
base = "Select objects with a similar value\n\n" "SHIFT+CLICK remove from selection set."
base = "Select objects with a similar value\n\nSHIFT+CLICK remove from selection set.\nALT+CLICK also unhide hidden objects (viewport and local hide)."
key = getattr(properties, "key", None)
active = context.active_object
@@ -1481,6 +1487,7 @@ class SelectSimilar(Operator):
def invoke(self, context, event):
self.calculate_sum = event.ctrl and event.type == "LEFTMOUSE"
self.remove_from_selection = event.shift and event.type == "LEFTMOUSE"
self.should_unhide = event.alt
return self.execute(context)
def execute(self, context):
@@ -1538,11 +1545,15 @@ class SelectSimilar(Operator):
def _select_objects(self, context, key, reference_values, tolerance):
count = 0
for obj in context.visible_objects:
objects = context.scene.objects if self.should_unhide else context.visible_objects
for obj in objects:
obj_value = self._get_value(obj, key)
if obj_value is None:
continue
if any(self._compare_values(obj_value, ref_value, tolerance) for ref_value in reference_values):
if self.should_unhide:
obj.hide_viewport = False
obj.hide_set(False)
obj.select_set(not self.remove_from_selection)
count += 1
return count
@@ -284,22 +284,33 @@ class SelectContainer(bpy.types.Operator):
class SelectSimilarContainer(bpy.types.Operator):
bl_idname = "bim.select_similar_container"
bl_label = "Select Similar Container"
bl_description = "Recurvisevly selects all objects in the container.\n\nCtrl+click to select only one level deep"
bl_description = "Recursively selects all objects in the container.\n\nCtrl+click to select only one level deep\nAlt+click to also unhide hidden objects (viewport and local hide)"
bl_options = {"REGISTER", "UNDO"}
container: bpy.props.IntProperty(default=0)
is_recursive: bpy.props.BoolProperty(default=True)
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
if event.type == "LEFTMOUSE" and event.ctrl:
self.is_recursive = False
self.should_unhide = event.alt
return self.execute(context)
def execute(self, context):
if self.container:
container = tool.Ifc.get().by_id(self.container)
elif element := tool.Ifc.get_entity(context.active_object):
container = ifcopenshell.util.element.get_container(element)
else:
return {"CANCELLED"}
if not container:
return {"CANCELLED"}
core.select_similar_container(
tool.Ifc,
tool.Spatial,
obj=context.active_object,
container=container,
is_recursive=self.is_recursive,
should_unhide=self.should_unhide,
)
self.is_recursive = True # <-- forcibly reset
return {"FINISHED"}
@@ -425,24 +436,29 @@ class SelectDecomposedElements(bpy.types.Operator):
should_filter: bpy.props.BoolProperty(name="Should Filter", default=True, options={"SKIP_SAVE"})
container: bpy.props.IntProperty()
is_recursive: bpy.props.BoolProperty(default=True, options={"SKIP_SAVE"})
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
@classmethod
def description(cls, context, operator):
return (
"Select the active item"
+ "\nALT+CLICK to select all listed elements.\nCTRL + CLICK to select only one level deep"
+ "\nSHIFT+CLICK to select all listed elements.\nCTRL+CLICK to select only one level deep"
+ "\nALT+CLICK to also unhide hidden objects (viewport and local hide)"
)
def invoke(self, context, event):
if event.type == "LEFTMOUSE":
if event.alt:
if event.shift:
self.should_filter = False
if event.ctrl:
self.is_recursive = False
self.should_unhide = event.alt
return self.execute(context)
def execute(self, context):
tool.Spatial.select_products(tool.Spatial.get_filtered_elements(self.should_filter, self.is_recursive))
tool.Spatial.select_products(
tool.Spatial.get_filtered_elements(self.should_filter, self.is_recursive), unhide=self.should_unhide
)
# Make selected active element in list, the active object
props = tool.Spatial.get_spatial_props()
@@ -525,6 +541,11 @@ class SetContainerVisibility(bpy.types.Operator):
if obj := tool.Ifc.get_object(container):
if collection := tool.Blender.get_object_bim_props(obj).collection:
collection.hide_viewport = should_hide
if not should_hide:
for element in tool.Spatial.get_decomposed_elements(container, is_recursive=False):
if element_obj := tool.Ifc.get_object(element):
element_obj.hide_viewport = False
element_obj.hide_set(False)
if self.should_include_children:
queue.extend(ifcopenshell.util.element.get_parts(container))
return {"FINISHED"}
+5 -2
View File
@@ -139,7 +139,7 @@ class BIM_PT_spatial_decomposition(Panel):
op = col.operator("bim.set_default_container", icon="OUTLINER_COLLECTION", text="Set Default")
op.container = ifc_definition_id
if tool.Blender.get_addon_preferences().container_hide_show_isolate:
if tool.Blender.get_addon_preferences().show_container_tools:
op = row.operator("bim.set_container_visibility", icon="FULLSCREEN_EXIT", text="")
op.mode = "ISOLATE"
op.container = ifc_definition_id
@@ -152,7 +152,10 @@ class BIM_PT_spatial_decomposition(Panel):
# The only operator that's enabled for IfcProject.
col = row.column(align=True)
col.operator("bim.select_container", icon="OBJECT_DATA", text="").container = ifc_definition_id
row_ = col.row(align=True)
row_.operator("bim.select_container", icon="OBJECT_DATA", text="").container = ifc_definition_id
if tool.Blender.get_addon_preferences().show_container_tools:
row_.operator("bim.select_similar_container", icon="RESTRICT_SELECT_OFF", text="").container = ifc_definition_id
col = row.column(align=True)
op = col.operator("bim.delete_container", icon="X", text="")
+11 -1
View File
@@ -221,10 +221,17 @@ class SelectType(bpy.types.Operator):
class SelectSimilarType(bpy.types.Operator):
"""Select Similar Type\nALT+Click to also unhide hidden objects (viewport and local hide)"""
bl_idname = "bim.select_similar_type"
bl_label = "Select Similar Type"
bl_options = {"REGISTER", "UNDO"}
related_object: bpy.props.StringProperty()
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
def invoke(self, context, event):
self.should_unhide = event.alt
return self.execute(context)
def execute(self, context):
self.file = tool.Ifc.get()
@@ -246,7 +253,10 @@ class SelectSimilarType(bpy.types.Operator):
for element in related_objects:
obj = tool.Ifc.get_object(element)
if obj and obj in context.visible_objects:
if obj and (self.should_unhide or obj in context.visible_objects):
if self.should_unhide:
obj.hide_viewport = False
obj.hide_set(False)
obj.select_set(True)
# copy selection query to clipboard
+5 -5
View File
@@ -714,9 +714,9 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
description="Default parameters for BIM elements",
)
container_hide_show_isolate: BoolProperty(
name="Container hide/show/isolate",
description="Enable container hide/show/isolate feature in the UI",
show_container_tools: BoolProperty(
name="Show Container Tools (Select, Hide, Show, Isolate)",
description="Enable container select, hide/show/isolate tools in the UI",
default=False,
)
@@ -786,7 +786,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
pset_dir: str
doc: DocPreferences
default_parameters: DefaultParameters
container_hide_show_isolate: bool
show_container_tools: bool
chain_filter_with_set_operations: bool
save_metadata_blend_file: bool
metadata_blend_file_suffix: str
@@ -984,7 +984,7 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences):
layout.prop(self, "bsdd_baseurl")
def draw_extras_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None:
layout.prop(self, "container_hide_show_isolate")
layout.prop(self, "show_container_tools")
row = layout.row(align=True)
row.prop(self, "chain_filter_with_set_operations")
row.operator("bim.open_uri", text="", icon="HELP").uri = "https://community.osarch.org/discussion/3270"
+5 -2
View File
@@ -82,9 +82,12 @@ def disable_editing_materials(material: type[tool.Material]) -> None:
def select_by_material(
material_tool: type[tool.Material], spatial: type[tool.Spatial], material: ifcopenshell.entity_instance
material_tool: type[tool.Material],
spatial: type[tool.Spatial],
material: ifcopenshell.entity_instance,
should_unhide: bool = False,
) -> None:
spatial.select_products(material_tool.get_elements_by_material(material))
spatial.select_products(material_tool.get_elements_by_material(material), unhide=should_unhide)
def enable_editing_material(material_tool: type[tool.Material], material: ifcopenshell.entity_instance) -> None:
+3 -5
View File
@@ -120,14 +120,12 @@ def select_container(
def select_similar_container(
ifc: type[tool.Ifc],
spatial: type[tool.Spatial],
obj: bpy.types.Object,
container: ifcopenshell.entity_instance,
is_recursive: bool = True,
should_unhide: bool = False,
) -> None:
element = ifc.get_entity(obj)
if element:
spatial.select_products(spatial.get_decomposed_elements(spatial.get_container(element), is_recursive))
spatial.select_products(spatial.get_decomposed_elements(container, is_recursive), unhide=should_unhide)
def select_product(spatial: type[tool.Spatial], product: ifcopenshell.entity_instance) -> None:
+1
View File
@@ -200,6 +200,7 @@ class Spatial(bonsai.core.tool.Spatial):
obj = tool.Ifc.get_object(product)
if obj and view_layer.objects.get(obj.name):
if unhide:
obj.hide_viewport = False
obj.hide_set(False)
obj.select_set(True)