mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Merge PR #7940 (Concatenate_selections) to resolve build conflicts
Both branches grew from Unhide_with_alt_click and rewrote the same select operators. This merge makes #7940 an ancestor so both PRs can coexist in BonsaiPR builds, reconciling intents: - select_aggregate: #7940 aggregate dedup dict + #8241 keep_current_selection/active-object seeds - SelectByMaterial: #7940 selection-derived materials + #8241 remove/filter threading (active object only in those modes) - SelectIfcClass: #7940 concatenated clipboard query + #8241 filter branch and deselect path - SelectSimilarContainer: #7940 multi-container derivation and clipboard + #8241 remove/filter threading (active object only in those modes) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -291,13 +291,13 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
||||
objects = [context.active_object] if context.active_object else []
|
||||
else:
|
||||
objects = context.selected_objects
|
||||
all_parts = []
|
||||
aggregates = {}
|
||||
for obj in objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element:
|
||||
aggregate = ifcopenshell.util.element.get_aggregate(element)
|
||||
if aggregate:
|
||||
all_parts.append(aggregate)
|
||||
aggregates[aggregate.id()] = aggregate
|
||||
if not keep_current_selection:
|
||||
obj.select_set(False)
|
||||
else:
|
||||
@@ -305,6 +305,8 @@ class BIM_OT_select_aggregate(bpy.types.Operator):
|
||||
if not element and not keep_current_selection:
|
||||
obj.select_set(False)
|
||||
|
||||
all_parts = list(aggregates.values())
|
||||
|
||||
if self.select_parts:
|
||||
selected_parts = []
|
||||
|
||||
|
||||
@@ -75,27 +75,109 @@ class SelectByMaterial(bpy.types.Operator):
|
||||
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,
|
||||
should_unhide=self.should_unhide,
|
||||
remove_from_selection=self.remove_from_selection,
|
||||
filter_selection=self.filter_selection,
|
||||
)
|
||||
# Determine the layer index hint from the explicit material prop, if any.
|
||||
# When the user clicks a specific layer in the UI, self.material is that
|
||||
# layer's IfcMaterial. We find its index so we can pull the same layer
|
||||
# from every other selected object's layer set.
|
||||
layer_index = None
|
||||
if self.material:
|
||||
ref_mat = tool.Ifc.get().by_id(self.material)
|
||||
layer_index = self._get_layer_index(ref_mat)
|
||||
|
||||
# copy selection query to clipboard
|
||||
if material.is_a("IfcMaterialLayerSet"):
|
||||
material_name = material.LayerSetName
|
||||
if self.remove_from_selection or self.filter_selection:
|
||||
objects = [context.active_object] if context.active_object else []
|
||||
else:
|
||||
material_name = material.Name
|
||||
result = f'material="{material_name}"'
|
||||
objects = context.selected_objects
|
||||
materials = {}
|
||||
for obj in objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
mat = ifcopenshell.util.element.get_material(element)
|
||||
if not mat:
|
||||
continue
|
||||
|
||||
resolved = self._resolve_material(mat, layer_index)
|
||||
if resolved:
|
||||
materials[resolved.id()] = resolved
|
||||
|
||||
# Fall back to the explicit material prop if selection yields nothing
|
||||
if not materials and self.material:
|
||||
materials = {self.material: tool.Ifc.get().by_id(self.material)}
|
||||
|
||||
if not materials:
|
||||
return {"FINISHED"}
|
||||
|
||||
for mat in materials.values():
|
||||
core.select_by_material(
|
||||
tool.Material,
|
||||
tool.Spatial,
|
||||
material=mat,
|
||||
should_unhide=self.should_unhide,
|
||||
remove_from_selection=self.remove_from_selection,
|
||||
filter_selection=self.filter_selection,
|
||||
)
|
||||
|
||||
result = " + ".join(f'material = "{self._get_name(m)}"' for m in materials.values())
|
||||
bpy.context.window_manager.clipboard = result
|
||||
self.report({"INFO"}, f"({result}) was copied to the clipboard.")
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
def _get_layer_index(self, material):
|
||||
"""Return the 0-based layer index if material is an IfcMaterial inside a layer set."""
|
||||
if not material.is_a("IfcMaterial"):
|
||||
return None
|
||||
ifc = tool.Ifc.get()
|
||||
for layer in ifc.get_inverse(material):
|
||||
if not layer.is_a("IfcMaterialLayer"):
|
||||
continue
|
||||
for layer_set in ifc.get_inverse(layer):
|
||||
if not layer_set.is_a("IfcMaterialLayerSet"):
|
||||
continue
|
||||
layers = list(layer_set.MaterialLayers)
|
||||
if layer in layers:
|
||||
return layers.index(layer)
|
||||
return None
|
||||
|
||||
def _resolve_material(self, mat, layer_index):
|
||||
"""Resolve an assigned material to the specific entity to select/name by.
|
||||
|
||||
When layer_index is set, drills into the layer set and returns the
|
||||
IfcMaterial at that index (or None if the set has fewer layers).
|
||||
Otherwise returns the layer set / profile set / constituent set itself.
|
||||
"""
|
||||
if mat.is_a("IfcMaterialLayerSetUsage"):
|
||||
mat = mat.ForLayerSet
|
||||
elif mat.is_a("IfcMaterialProfileSetUsage"):
|
||||
mat = mat.ForProfileSet
|
||||
|
||||
if layer_index is not None and mat.is_a("IfcMaterialLayerSet"):
|
||||
layers = list(mat.MaterialLayers)
|
||||
if layer_index < len(layers):
|
||||
return layers[layer_index].Material
|
||||
return None
|
||||
|
||||
return mat
|
||||
|
||||
def _get_name(self, material):
|
||||
if material.is_a("IfcMaterialLayerSet"):
|
||||
if material.LayerSetName:
|
||||
return material.LayerSetName
|
||||
names = [l.Material.Name for l in (material.MaterialLayers or []) if l.Material and l.Material.Name]
|
||||
return ", ".join(names) if names else material.is_a()
|
||||
if material.is_a("IfcMaterialProfileSet"):
|
||||
if material.Name:
|
||||
return material.Name
|
||||
names = [p.Material.Name for p in (material.MaterialProfiles or []) if p.Material and p.Material.Name]
|
||||
return ", ".join(names) if names else material.is_a()
|
||||
if material.is_a("IfcMaterialConstituentSet"):
|
||||
if material.Name:
|
||||
return material.Name
|
||||
names = [c.Material.Name for c in (material.MaterialConstituents or []) if c.Material and c.Material.Name]
|
||||
return ", ".join(names) if names else material.is_a()
|
||||
return getattr(material, "Name", None) or material.is_a()
|
||||
|
||||
|
||||
class EnableEditingMaterial(bpy.types.Operator):
|
||||
bl_idname = "bim.enable_editing_material"
|
||||
|
||||
@@ -1297,7 +1297,6 @@ class SelectIfcClass(Operator):
|
||||
if not element or not any(element.is_a(cls) for cls in classes):
|
||||
obj.select_set(False)
|
||||
return {"FINISHED"}
|
||||
result = ""
|
||||
for cls in classes:
|
||||
for element in tool.Ifc.get().by_type(cls):
|
||||
if (
|
||||
@@ -1314,13 +1313,10 @@ class SelectIfcClass(Operator):
|
||||
else:
|
||||
tool.Blender.select_object(obj)
|
||||
|
||||
# copy selection query to clipboard
|
||||
if not result:
|
||||
result = f"{cls}"
|
||||
else:
|
||||
result += f", {cls}"
|
||||
bpy.context.window_manager.clipboard = result
|
||||
self.report({"INFO"}, f"({result}) was copied to the clipboard.")
|
||||
# copy selection query to clipboard
|
||||
result = " + ".join(classes)
|
||||
bpy.context.window_manager.clipboard = result
|
||||
self.report({"INFO"}, f"({result}) was copied to the clipboard.")
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1541,7 +1537,7 @@ class SelectSimilar(Operator):
|
||||
f"{verb} all objects that share the same ({self.key}) value(s) from {len(reference_values)} reference object(s).",
|
||||
)
|
||||
|
||||
self._generate_clipboard_query(reference_values[0] if reference_values else None, key)
|
||||
self._generate_clipboard_query(reference_values, key)
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1601,17 +1597,22 @@ class SelectSimilar(Operator):
|
||||
bpy.context.window_manager.clipboard = str(total)
|
||||
self.report({"INFO"}, f"({total}) was copied to the clipboard.")
|
||||
|
||||
def _generate_clipboard_query(self, value, key):
|
||||
def _generate_clipboard_query(self, values, key):
|
||||
key = "PredefinedType" if key == "predefined_type" else key
|
||||
if value is True:
|
||||
value = "TRUE"
|
||||
elif value is False:
|
||||
value = "FALSE"
|
||||
if not values:
|
||||
return
|
||||
|
||||
if isinstance(value, list) and value:
|
||||
result = ", ".join(f'{key} = "{item}"' for item in value)
|
||||
else:
|
||||
result = f'{key} = "{value}"'
|
||||
def format_value(value):
|
||||
if value is True:
|
||||
return f'{key} = "TRUE"'
|
||||
elif value is False:
|
||||
return f'{key} = "FALSE"'
|
||||
elif isinstance(value, list) and value:
|
||||
return ", ".join(f'{key} = "{item}"' for item in value)
|
||||
else:
|
||||
return f'{key} = "{value}"'
|
||||
|
||||
result = " + ".join(format_value(v) for v in values)
|
||||
|
||||
bpy.context.window_manager.clipboard = result
|
||||
self.report({"INFO"}, f"({result}) was copied to the clipboard.")
|
||||
|
||||
@@ -303,21 +303,40 @@ class SelectSimilarContainer(bpy.types.Operator):
|
||||
|
||||
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)
|
||||
# Called from container manager panel with explicit container
|
||||
ifc_container = tool.Ifc.get().by_id(self.container)
|
||||
containers = {ifc_container.id(): ifc_container} if ifc_container else {}
|
||||
else:
|
||||
# Called from 3D viewport — derive containers from the selected objects
|
||||
# (active object only in remove/filter mode, so a single criteria source)
|
||||
if self.remove_from_selection or self.filter_selection:
|
||||
objects = [context.active_object] if context.active_object else []
|
||||
else:
|
||||
objects = context.selected_objects or [context.active_object]
|
||||
containers = {}
|
||||
for obj in objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
container = tool.Spatial.get_container(element)
|
||||
if container:
|
||||
containers[container.id()] = container
|
||||
|
||||
if not containers:
|
||||
return {"CANCELLED"}
|
||||
if not container:
|
||||
return {"CANCELLED"}
|
||||
core.select_similar_container(
|
||||
tool.Spatial,
|
||||
container=container,
|
||||
is_recursive=self.is_recursive,
|
||||
should_unhide=self.should_unhide,
|
||||
remove_from_selection=self.remove_from_selection,
|
||||
filter_selection=self.filter_selection,
|
||||
)
|
||||
|
||||
for container in containers.values():
|
||||
tool.Spatial.select_products(
|
||||
tool.Spatial.get_decomposed_elements(container, self.is_recursive),
|
||||
unhide=self.should_unhide,
|
||||
remove=self.remove_from_selection,
|
||||
filter_selection=self.filter_selection,
|
||||
)
|
||||
|
||||
result = " + ".join(f'location = "{c.Name}"' for c in containers.values())
|
||||
bpy.context.window_manager.clipboard = result
|
||||
self.report({"INFO"}, f"({result}) was copied to the clipboard.")
|
||||
|
||||
self.is_recursive = True # <-- forcibly reset
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user