mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 23:31:00 +00:00
Add CTRL+Click filter-selection mode to select operators
CTRL+Click keeps only the already selected objects that match the criteria (from the active object, or the clicked UI item), selecting nothing new. Applied to the same operators as SHIFT+Click remove: select_similar, select_ifc_class, select_similar_type, select_by_material, select_similar_container, select_decomposed_elements, select_group_elements, select_aggregate and select_linked_aggregates, threaded through Spatial.select_products(filter_selection=...). Modifier scheme is now uniform: Click selects, SHIFT removes, CTRL filters, ALT unhides. Legacy plain-CTRL functions moved to CTRL+SHIFT (one-level-deep, exclude-children, calculate-sum). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -268,23 +268,26 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
|||||||
)
|
)
|
||||||
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def description(cls, context, properties):
|
def description(cls, context, properties):
|
||||||
if properties.select_parts:
|
if properties.select_parts:
|
||||||
return "Select Aggregate and Parts.\n\nCtrl+click to select only one level deep\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)"
|
return "Select Aggregate and Parts.\n\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nCTRL+SHIFT+Click to select only one level deep\nALT+Click to also unhide hidden objects (viewport and local hide)"
|
||||||
else:
|
else:
|
||||||
return "Select Aggregate\n\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)"
|
return "Select Aggregate\n\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nALT+Click to also unhide hidden objects (viewport and local hide)"
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
if event.type == "LEFTMOUSE" and event.ctrl:
|
if event.type == "LEFTMOUSE" and event.ctrl and event.shift:
|
||||||
self.one_level_deep = True
|
self.one_level_deep = True
|
||||||
self.should_unhide = event.alt
|
self.should_unhide = event.alt
|
||||||
self.remove_from_selection = event.shift
|
self.remove_from_selection = event.shift and not event.ctrl
|
||||||
|
self.filter_selection = event.ctrl and not event.shift
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
if self.remove_from_selection:
|
keep_current_selection = self.remove_from_selection or self.filter_selection
|
||||||
|
if keep_current_selection:
|
||||||
objects = [context.active_object] if context.active_object else []
|
objects = [context.active_object] if context.active_object else []
|
||||||
else:
|
else:
|
||||||
objects = context.selected_objects
|
objects = context.selected_objects
|
||||||
@@ -295,11 +298,11 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
|||||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||||
if aggregate:
|
if aggregate:
|
||||||
all_parts.append(aggregate)
|
all_parts.append(aggregate)
|
||||||
if not self.remove_from_selection:
|
if not keep_current_selection:
|
||||||
obj.select_set(False)
|
obj.select_set(False)
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
if not element and not self.remove_from_selection:
|
if not element and not keep_current_selection:
|
||||||
obj.select_set(False)
|
obj.select_set(False)
|
||||||
|
|
||||||
if self.select_parts:
|
if self.select_parts:
|
||||||
@@ -323,24 +326,36 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
|||||||
|
|
||||||
add_descendants(subpart)
|
add_descendants(subpart)
|
||||||
|
|
||||||
for element in set(selected_parts + all_parts):
|
if self.filter_selection:
|
||||||
obj = tool.Ifc.get_object(element)
|
matched_objs = {tool.Ifc.get_object(element) for element in set(selected_parts + all_parts)}
|
||||||
if obj:
|
for obj in context.selected_objects:
|
||||||
if self.should_unhide:
|
if obj not in matched_objs:
|
||||||
obj.hide_viewport = False
|
obj.select_set(False)
|
||||||
obj.hide_set(False)
|
else:
|
||||||
obj.select_set(not self.remove_from_selection)
|
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(not self.remove_from_selection)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for aggregate_element in all_parts:
|
if self.filter_selection:
|
||||||
aggregate_obj = tool.Ifc.get_object(aggregate_element)
|
matched_objs = {tool.Ifc.get_object(element) for element in all_parts}
|
||||||
if aggregate_obj:
|
for obj in context.selected_objects:
|
||||||
if self.should_unhide:
|
if obj not in matched_objs:
|
||||||
aggregate_obj.hide_viewport = False
|
obj.select_set(False)
|
||||||
aggregate_obj.hide_set(False)
|
else:
|
||||||
aggregate_obj.select_set(not self.remove_from_selection)
|
for aggregate_element in all_parts:
|
||||||
if not self.remove_from_selection:
|
aggregate_obj = tool.Ifc.get_object(aggregate_element)
|
||||||
bpy.context.view_layer.objects.active = aggregate_obj
|
if aggregate_obj:
|
||||||
|
if self.should_unhide:
|
||||||
|
aggregate_obj.hide_viewport = False
|
||||||
|
aggregate_obj.hide_set(False)
|
||||||
|
aggregate_obj.select_set(not self.remove_from_selection)
|
||||||
|
if not self.remove_from_selection:
|
||||||
|
bpy.context.view_layer.objects.active = aggregate_obj
|
||||||
|
|
||||||
# copy selection query to clipboard
|
# copy selection query to clipboard
|
||||||
result = ""
|
result = ""
|
||||||
@@ -425,26 +440,30 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator):
|
|||||||
select_parts: bpy.props.BoolProperty(default=False)
|
select_parts: bpy.props.BoolProperty(default=False)
|
||||||
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def description(cls, context, properties):
|
def description(cls, context, properties):
|
||||||
if properties.select_parts:
|
if properties.select_parts:
|
||||||
return "Select all aggregates, subaggregates and all their parts\n\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)"
|
return "Select all aggregates, subaggregates and all their parts\n\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nALT+Click to also unhide hidden objects (viewport and local hide)"
|
||||||
else:
|
else:
|
||||||
return "Select all aggregates\n\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)"
|
return "Select all aggregates\n\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nALT+Click to also unhide hidden objects (viewport and local hide)"
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
self.should_unhide = event.alt
|
self.should_unhide = event.alt
|
||||||
self.remove_from_selection = event.shift
|
self.remove_from_selection = event.shift and not event.ctrl
|
||||||
|
self.filter_selection = event.ctrl and not event.shift
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
if self.remove_from_selection:
|
keep_current_selection = self.remove_from_selection or self.filter_selection
|
||||||
|
if keep_current_selection:
|
||||||
objects = [context.active_object] if context.active_object else []
|
objects = [context.active_object] if context.active_object else []
|
||||||
else:
|
else:
|
||||||
objects = context.selected_objects
|
objects = context.selected_objects
|
||||||
|
matched_objs = set()
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
if not self.remove_from_selection:
|
if not keep_current_selection:
|
||||||
obj.select_set(False)
|
obj.select_set(False)
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||||
@@ -474,6 +493,9 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator):
|
|||||||
for element in parts_objs:
|
for element in parts_objs:
|
||||||
obj = tool.Ifc.get_object(element)
|
obj = tool.Ifc.get_object(element)
|
||||||
if obj:
|
if obj:
|
||||||
|
if self.filter_selection:
|
||||||
|
matched_objs.add(obj)
|
||||||
|
continue
|
||||||
if self.should_unhide:
|
if self.should_unhide:
|
||||||
obj.hide_viewport = False
|
obj.hide_viewport = False
|
||||||
obj.hide_set(False)
|
obj.hide_set(False)
|
||||||
@@ -482,11 +504,19 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator):
|
|||||||
for element in parts:
|
for element in parts:
|
||||||
obj = tool.Ifc.get_object(element)
|
obj = tool.Ifc.get_object(element)
|
||||||
if obj:
|
if obj:
|
||||||
|
if self.filter_selection:
|
||||||
|
matched_objs.add(obj)
|
||||||
|
continue
|
||||||
if self.should_unhide:
|
if self.should_unhide:
|
||||||
obj.hide_viewport = False
|
obj.hide_viewport = False
|
||||||
obj.hide_set(False)
|
obj.hide_set(False)
|
||||||
obj.select_set(not self.remove_from_selection)
|
obj.select_set(not self.remove_from_selection)
|
||||||
|
|
||||||
|
if self.filter_selection:
|
||||||
|
for obj in context.selected_objects:
|
||||||
|
if obj not in matched_objs:
|
||||||
|
obj.select_set(False)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -198,18 +198,21 @@ class SelectGroupElements(bpy.types.Operator):
|
|||||||
bl_description = (
|
bl_description = (
|
||||||
"Select objects assigned to the selected group and all nested groups"
|
"Select objects assigned to the selected group and all nested groups"
|
||||||
"\nSHIFT + CLICK to remove from selection set"
|
"\nSHIFT + CLICK to remove from selection set"
|
||||||
"\nCTRL + CLICK to exclude children"
|
"\nCTRL + CLICK to filter selection to matching objects only"
|
||||||
|
"\nCTRL + SHIFT + CLICK to exclude children"
|
||||||
"\nALT + CLICK to also unhide hidden objects (viewport and local hide)"
|
"\nALT + CLICK to also unhide hidden objects (viewport and local hide)"
|
||||||
)
|
)
|
||||||
group: bpy.props.IntProperty()
|
group: bpy.props.IntProperty()
|
||||||
is_recursive: bpy.props.BoolProperty(name="Is Recursive", default=True, options={"SKIP_SAVE"})
|
is_recursive: bpy.props.BoolProperty(name="Is Recursive", default=True, options={"SKIP_SAVE"})
|
||||||
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
self.is_recursive = not event.ctrl
|
self.is_recursive = not (event.ctrl and event.shift)
|
||||||
self.should_unhide = event.alt
|
self.should_unhide = event.alt
|
||||||
self.remove_from_selection = event.shift
|
self.remove_from_selection = event.shift and not event.ctrl
|
||||||
|
self.filter_selection = event.ctrl and not event.shift
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
@@ -217,6 +220,7 @@ class SelectGroupElements(bpy.types.Operator):
|
|||||||
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,
|
unhide=self.should_unhide,
|
||||||
remove=self.remove_from_selection,
|
remove=self.remove_from_selection,
|
||||||
|
filter_selection=self.filter_selection,
|
||||||
)
|
)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|||||||
@@ -61,15 +61,17 @@ class DisableEditingMaterials(bpy.types.Operator):
|
|||||||
class SelectByMaterial(bpy.types.Operator):
|
class SelectByMaterial(bpy.types.Operator):
|
||||||
bl_idname = "bim.select_by_material"
|
bl_idname = "bim.select_by_material"
|
||||||
bl_label = "Select By Material"
|
bl_label = "Select By Material"
|
||||||
bl_description = "Select objects using the provided material\n\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)"
|
bl_description = "Select objects using the provided material\n\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nALT+Click to also unhide hidden objects (viewport and local hide)"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
material: bpy.props.IntProperty()
|
material: bpy.props.IntProperty()
|
||||||
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
self.should_unhide = event.alt
|
self.should_unhide = event.alt
|
||||||
self.remove_from_selection = event.shift
|
self.remove_from_selection = event.shift and not event.ctrl
|
||||||
|
self.filter_selection = event.ctrl and not event.shift
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
@@ -80,6 +82,7 @@ class SelectByMaterial(bpy.types.Operator):
|
|||||||
material=material,
|
material=material,
|
||||||
should_unhide=self.should_unhide,
|
should_unhide=self.should_unhide,
|
||||||
remove_from_selection=self.remove_from_selection,
|
remove_from_selection=self.remove_from_selection,
|
||||||
|
filter_selection=self.filter_selection,
|
||||||
)
|
)
|
||||||
|
|
||||||
# copy selection query to clipboard
|
# copy selection query to clipboard
|
||||||
|
|||||||
@@ -1264,7 +1264,7 @@ class SelectGlobalId(Operator):
|
|||||||
|
|
||||||
|
|
||||||
class SelectIfcClass(Operator):
|
class SelectIfcClass(Operator):
|
||||||
"""Click to select all objects that match with the given IFC class\nSHIFT + Click to remove from selection set\nALT + Click to also unhide hidden objects (viewport and local hide)"""
|
"""Click to select all objects that match with the given IFC class\nSHIFT + Click to remove from selection set\nCTRL + Click to filter selection to matching objects only\nALT + Click to also unhide hidden objects (viewport and local hide)"""
|
||||||
|
|
||||||
bl_idname = "bim.select_ifc_class"
|
bl_idname = "bim.select_ifc_class"
|
||||||
bl_label = "Select IFC Class"
|
bl_label = "Select IFC Class"
|
||||||
@@ -1272,14 +1272,16 @@ class SelectIfcClass(Operator):
|
|||||||
should_filter_predefined_type: BoolProperty(default=False)
|
should_filter_predefined_type: BoolProperty(default=False)
|
||||||
should_unhide: BoolProperty(default=False)
|
should_unhide: BoolProperty(default=False)
|
||||||
remove_from_selection: BoolProperty(default=False, options={"SKIP_SAVE"})
|
remove_from_selection: BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
filter_selection: BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
self.remove_from_selection = event.shift
|
self.remove_from_selection = event.shift and not event.ctrl
|
||||||
|
self.filter_selection = event.ctrl and not event.shift
|
||||||
self.should_unhide = event.alt
|
self.should_unhide = event.alt
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
if self.remove_from_selection:
|
if self.remove_from_selection or self.filter_selection:
|
||||||
objects = [context.active_object] if context.active_object else []
|
objects = [context.active_object] if context.active_object else []
|
||||||
else:
|
else:
|
||||||
objects = context.selected_objects
|
objects = context.selected_objects
|
||||||
@@ -1289,6 +1291,12 @@ class SelectIfcClass(Operator):
|
|||||||
if element := tool.Ifc.get_entity(obj):
|
if element := tool.Ifc.get_entity(obj):
|
||||||
classes.add(element.is_a())
|
classes.add(element.is_a())
|
||||||
predefined_types.add(ifcopenshell.util.element.get_predefined_type(element))
|
predefined_types.add(ifcopenshell.util.element.get_predefined_type(element))
|
||||||
|
if self.filter_selection:
|
||||||
|
for obj in context.selected_objects:
|
||||||
|
element = tool.Ifc.get_entity(obj)
|
||||||
|
if not element or not any(element.is_a(cls) for cls in classes):
|
||||||
|
obj.select_set(False)
|
||||||
|
return {"FINISHED"}
|
||||||
result = ""
|
result = ""
|
||||||
for cls in classes:
|
for cls in classes:
|
||||||
for element in tool.Ifc.get().by_type(cls):
|
for element in tool.Ifc.get().by_type(cls):
|
||||||
@@ -1464,10 +1472,11 @@ class SelectSimilar(Operator):
|
|||||||
calculated_sum: bpy.props.FloatProperty(name="Calculated Sum", default=0.0)
|
calculated_sum: bpy.props.FloatProperty(name="Calculated Sum", default=0.0)
|
||||||
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def description(cls, context, properties):
|
def description(cls, context, properties):
|
||||||
base = "Select objects with a similar value\n\nSHIFT+CLICK remove from selection set.\nALT+CLICK also unhide hidden objects (viewport and local hide)."
|
base = "Select objects with a similar value\n\nSHIFT+CLICK remove from selection set.\nCTRL+CLICK filter selection to matching objects only.\nALT+CLICK also unhide hidden objects (viewport and local hide)."
|
||||||
|
|
||||||
key = getattr(properties, "key", None)
|
key = getattr(properties, "key", None)
|
||||||
active = context.active_object
|
active = context.active_object
|
||||||
@@ -1480,7 +1489,7 @@ class SelectSimilar(Operator):
|
|||||||
|
|
||||||
value = ifcopenshell.util.selector.get_element_value(element, key)
|
value = ifcopenshell.util.selector.get_element_value(element, key)
|
||||||
if isinstance(value, (int, float)):
|
if isinstance(value, (int, float)):
|
||||||
return base + ("\nCTRL+CLICK display the sum of all selected objects")
|
return base + ("\nCTRL+SHIFT+CLICK display the sum of all selected objects")
|
||||||
else:
|
else:
|
||||||
return base
|
return base
|
||||||
|
|
||||||
@@ -1492,8 +1501,9 @@ class SelectSimilar(Operator):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
self.calculate_sum = event.ctrl and event.type == "LEFTMOUSE"
|
self.calculate_sum = event.ctrl and event.shift and event.type == "LEFTMOUSE"
|
||||||
self.remove_from_selection = event.shift and event.type == "LEFTMOUSE"
|
self.remove_from_selection = event.shift and not event.ctrl and event.type == "LEFTMOUSE"
|
||||||
|
self.filter_selection = event.ctrl and not event.shift and event.type == "LEFTMOUSE"
|
||||||
self.should_unhide = event.alt
|
self.should_unhide = event.alt
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
@@ -1513,7 +1523,12 @@ class SelectSimilar(Operator):
|
|||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
matched_count = self._select_objects(context, key, reference_values, tolerance)
|
matched_count = self._select_objects(context, key, reference_values, tolerance)
|
||||||
verb = "Deselected" if self.remove_from_selection else "Selected"
|
if self.filter_selection:
|
||||||
|
verb = "Filtered selection to"
|
||||||
|
elif self.remove_from_selection:
|
||||||
|
verb = "Deselected"
|
||||||
|
else:
|
||||||
|
verb = "Selected"
|
||||||
|
|
||||||
if all(isinstance(v, (int, float)) for v in reference_values):
|
if all(isinstance(v, (int, float)) for v in reference_values):
|
||||||
self.report(
|
self.report(
|
||||||
@@ -1539,7 +1554,7 @@ class SelectSimilar(Operator):
|
|||||||
def _get_reference_values(self, context, key):
|
def _get_reference_values(self, context, key):
|
||||||
objects = (
|
objects = (
|
||||||
[context.active_object]
|
[context.active_object]
|
||||||
if self.remove_from_selection
|
if self.remove_from_selection or self.filter_selection
|
||||||
else (context.selected_objects or [context.active_object])
|
else (context.selected_objects or [context.active_object])
|
||||||
)
|
)
|
||||||
values = [self._get_value(obj, key) for obj in objects]
|
values = [self._get_value(obj, key) for obj in objects]
|
||||||
@@ -1552,6 +1567,17 @@ class SelectSimilar(Operator):
|
|||||||
|
|
||||||
def _select_objects(self, context, key, reference_values, tolerance):
|
def _select_objects(self, context, key, reference_values, tolerance):
|
||||||
count = 0
|
count = 0
|
||||||
|
if self.filter_selection:
|
||||||
|
# Keep only the already selected objects that match, select nothing new.
|
||||||
|
for obj in context.selected_objects:
|
||||||
|
obj_value = self._get_value(obj, key)
|
||||||
|
if obj_value is not None and any(
|
||||||
|
self._compare_values(obj_value, ref_value, tolerance) for ref_value in reference_values
|
||||||
|
):
|
||||||
|
count += 1
|
||||||
|
else:
|
||||||
|
obj.select_set(False)
|
||||||
|
return count
|
||||||
objects = context.scene.objects if self.should_unhide else context.visible_objects
|
objects = context.scene.objects if self.should_unhide else context.visible_objects
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
obj_value = self._get_value(obj, key)
|
obj_value = self._get_value(obj, key)
|
||||||
|
|||||||
@@ -284,19 +284,21 @@ class SelectContainer(bpy.types.Operator):
|
|||||||
class SelectSimilarContainer(bpy.types.Operator):
|
class SelectSimilarContainer(bpy.types.Operator):
|
||||||
bl_idname = "bim.select_similar_container"
|
bl_idname = "bim.select_similar_container"
|
||||||
bl_label = "Select Similar Container"
|
bl_label = "Select Similar Container"
|
||||||
bl_description = "Recursively selects all objects in the container.\n\nCtrl+click to select only one level deep\nShift+click to remove from selection set\nAlt+click to also unhide hidden objects (viewport and local hide)"
|
bl_description = "Recursively selects all objects in the container.\n\nShift+click to remove from selection set\nCtrl+click to filter selection to matching objects only\nCtrl+Shift+click to select only one level deep\nAlt+click to also unhide hidden objects (viewport and local hide)"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
container: bpy.props.IntProperty(default=0)
|
container: bpy.props.IntProperty(default=0)
|
||||||
is_recursive: bpy.props.BoolProperty(default=True)
|
is_recursive: bpy.props.BoolProperty(default=True)
|
||||||
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
if event.type == "LEFTMOUSE" and event.ctrl:
|
if event.type == "LEFTMOUSE" and event.ctrl and event.shift:
|
||||||
self.is_recursive = False
|
self.is_recursive = False
|
||||||
self.should_unhide = event.alt
|
self.should_unhide = event.alt
|
||||||
self.remove_from_selection = event.shift
|
self.remove_from_selection = event.shift and not event.ctrl
|
||||||
|
self.filter_selection = event.ctrl and not event.shift
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
@@ -314,6 +316,7 @@ class SelectSimilarContainer(bpy.types.Operator):
|
|||||||
is_recursive=self.is_recursive,
|
is_recursive=self.is_recursive,
|
||||||
should_unhide=self.should_unhide,
|
should_unhide=self.should_unhide,
|
||||||
remove_from_selection=self.remove_from_selection,
|
remove_from_selection=self.remove_from_selection,
|
||||||
|
filter_selection=self.filter_selection,
|
||||||
)
|
)
|
||||||
self.is_recursive = True # <-- forcibly reset
|
self.is_recursive = True # <-- forcibly reset
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
@@ -441,21 +444,24 @@ class SelectDecomposedElements(bpy.types.Operator):
|
|||||||
is_recursive: bpy.props.BoolProperty(default=True, options={"SKIP_SAVE"})
|
is_recursive: bpy.props.BoolProperty(default=True, options={"SKIP_SAVE"})
|
||||||
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def description(cls, context, operator):
|
def description(cls, context, operator):
|
||||||
return (
|
return (
|
||||||
"Select the active item"
|
"Select the active item"
|
||||||
+ "\nSHIFT+CLICK to remove from selection set.\nCTRL+CLICK to select only one level deep"
|
+ "\nSHIFT+CLICK to remove from selection set.\nCTRL+CLICK to filter selection to matching objects only"
|
||||||
|
+ "\nCTRL+SHIFT+CLICK to select only one level deep"
|
||||||
+ "\nALT+CLICK to also unhide hidden objects (viewport and local hide)"
|
+ "\nALT+CLICK to also unhide hidden objects (viewport and local hide)"
|
||||||
)
|
)
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
if event.type == "LEFTMOUSE":
|
if event.type == "LEFTMOUSE":
|
||||||
if event.ctrl:
|
if event.ctrl and event.shift:
|
||||||
self.is_recursive = False
|
self.is_recursive = False
|
||||||
self.should_unhide = event.alt
|
self.should_unhide = event.alt
|
||||||
self.remove_from_selection = event.shift
|
self.remove_from_selection = event.shift and not event.ctrl
|
||||||
|
self.filter_selection = event.ctrl and not event.shift
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
@@ -463,6 +469,7 @@ class SelectDecomposedElements(bpy.types.Operator):
|
|||||||
tool.Spatial.get_filtered_elements(self.should_filter, self.is_recursive),
|
tool.Spatial.get_filtered_elements(self.should_filter, self.is_recursive),
|
||||||
unhide=self.should_unhide,
|
unhide=self.should_unhide,
|
||||||
remove=self.remove_from_selection,
|
remove=self.remove_from_selection,
|
||||||
|
filter_selection=self.filter_selection,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make selected active element in list, the active object
|
# Make selected active element in list, the active object
|
||||||
@@ -474,7 +481,8 @@ class SelectDecomposedElements(bpy.types.Operator):
|
|||||||
obj = tool.Ifc.get_object(ifc_entity)
|
obj = tool.Ifc.get_object(ifc_entity)
|
||||||
if obj:
|
if obj:
|
||||||
context.view_layer.objects.active = obj
|
context.view_layer.objects.active = obj
|
||||||
obj.select_set(not self.remove_from_selection)
|
if not self.filter_selection:
|
||||||
|
obj.select_set(not self.remove_from_selection)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class SelectType(bpy.types.Operator):
|
|||||||
|
|
||||||
|
|
||||||
class SelectSimilarType(bpy.types.Operator):
|
class SelectSimilarType(bpy.types.Operator):
|
||||||
"""Select Similar Type\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)"""
|
"""Select Similar Type\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nALT+Click to also unhide hidden objects (viewport and local hide)"""
|
||||||
|
|
||||||
bl_idname = "bim.select_similar_type"
|
bl_idname = "bim.select_similar_type"
|
||||||
bl_label = "Select Similar Type"
|
bl_label = "Select Similar Type"
|
||||||
@@ -229,15 +229,17 @@ class SelectSimilarType(bpy.types.Operator):
|
|||||||
related_object: bpy.props.StringProperty()
|
related_object: bpy.props.StringProperty()
|
||||||
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
self.should_unhide = event.alt
|
self.should_unhide = event.alt
|
||||||
self.remove_from_selection = event.shift
|
self.remove_from_selection = event.shift and not event.ctrl
|
||||||
|
self.filter_selection = event.ctrl and not event.shift
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
self.file = tool.Ifc.get()
|
self.file = tool.Ifc.get()
|
||||||
if self.remove_from_selection:
|
if self.remove_from_selection or self.filter_selection:
|
||||||
objects = [context.active_object] if context.active_object else []
|
objects = [context.active_object] if context.active_object else []
|
||||||
else:
|
else:
|
||||||
objects = bpy.context.selected_objects
|
objects = bpy.context.selected_objects
|
||||||
@@ -252,6 +254,13 @@ class SelectSimilarType(bpy.types.Operator):
|
|||||||
continue
|
continue
|
||||||
relating_types.add(relating_type)
|
relating_types.add(relating_type)
|
||||||
|
|
||||||
|
if self.filter_selection:
|
||||||
|
for obj in context.selected_objects:
|
||||||
|
element = tool.Ifc.get_entity(obj)
|
||||||
|
if not element or ifcopenshell.util.element.get_type(element) not in relating_types:
|
||||||
|
obj.select_set(False)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
result = ""
|
result = ""
|
||||||
for relating_type in relating_types:
|
for relating_type in relating_types:
|
||||||
related_objects = ifcopenshell.util.element.get_types(relating_type)
|
related_objects = ifcopenshell.util.element.get_types(relating_type)
|
||||||
|
|||||||
@@ -87,9 +87,13 @@ def select_by_material(
|
|||||||
material: ifcopenshell.entity_instance,
|
material: ifcopenshell.entity_instance,
|
||||||
should_unhide: bool = False,
|
should_unhide: bool = False,
|
||||||
remove_from_selection: bool = False,
|
remove_from_selection: bool = False,
|
||||||
|
filter_selection: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
spatial.select_products(
|
spatial.select_products(
|
||||||
material_tool.get_elements_by_material(material), unhide=should_unhide, remove=remove_from_selection
|
material_tool.get_elements_by_material(material),
|
||||||
|
unhide=should_unhide,
|
||||||
|
remove=remove_from_selection,
|
||||||
|
filter_selection=filter_selection,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -125,9 +125,13 @@ def select_similar_container(
|
|||||||
is_recursive: bool = True,
|
is_recursive: bool = True,
|
||||||
should_unhide: bool = False,
|
should_unhide: bool = False,
|
||||||
remove_from_selection: bool = False,
|
remove_from_selection: bool = False,
|
||||||
|
filter_selection: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
spatial.select_products(
|
spatial.select_products(
|
||||||
spatial.get_decomposed_elements(container, is_recursive), unhide=should_unhide, remove=remove_from_selection
|
spatial.get_decomposed_elements(container, is_recursive),
|
||||||
|
unhide=should_unhide,
|
||||||
|
remove=remove_from_selection,
|
||||||
|
filter_selection=filter_selection,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -975,7 +975,7 @@ class Spatial:
|
|||||||
def run_spatial_assign_container(cls, container, objs): pass
|
def run_spatial_assign_container(cls, container, objs): pass
|
||||||
def run_spatial_import_spatial_decomposition(cls): pass
|
def run_spatial_import_spatial_decomposition(cls): pass
|
||||||
def select_object(cls, obj): pass
|
def select_object(cls, obj): pass
|
||||||
def select_products(cls, products, unhide=False, remove=False): pass
|
def select_products(cls, products, unhide=False, remove=False, filter_selection=False): pass
|
||||||
def set_active_object(cls, obj, selection_mode=None): pass
|
def set_active_object(cls, obj, selection_mode=None): pass
|
||||||
def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass
|
def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass
|
||||||
def set_target_container_as_default(cls): pass
|
def set_target_container_as_default(cls): pass
|
||||||
|
|||||||
@@ -193,11 +193,26 @@ class Spatial(bonsai.core.tool.Spatial):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def select_products(
|
def select_products(
|
||||||
cls, products: Iterable[ifcopenshell.entity_instance], unhide: bool = False, remove: bool = False
|
cls,
|
||||||
|
products: Iterable[ifcopenshell.entity_instance],
|
||||||
|
unhide: bool = False,
|
||||||
|
remove: bool = False,
|
||||||
|
filter_selection: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
assert (view_layer := bpy.context.view_layer)
|
assert (view_layer := bpy.context.view_layer)
|
||||||
# Update view layer, otherwise `objects` might be missing just created objects.
|
# Update view layer, otherwise `objects` might be missing just created objects.
|
||||||
view_layer.update()
|
view_layer.update()
|
||||||
|
if filter_selection:
|
||||||
|
# Keep only the already selected objects that match, select nothing new.
|
||||||
|
matched_objs = set()
|
||||||
|
for product in products:
|
||||||
|
obj = tool.Ifc.get_object(product)
|
||||||
|
if obj and view_layer.objects.get(obj.name):
|
||||||
|
matched_objs.add(obj)
|
||||||
|
for obj in bpy.context.selected_objects:
|
||||||
|
if obj not in matched_objs:
|
||||||
|
obj.select_set(False)
|
||||||
|
return
|
||||||
for product in products:
|
for product in products:
|
||||||
obj = tool.Ifc.get_object(product)
|
obj = tool.Ifc.get_object(product)
|
||||||
if obj and view_layer.objects.get(obj.name):
|
if obj and view_layer.objects.get(obj.name):
|
||||||
|
|||||||
Reference in New Issue
Block a user