mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
extend https://github.com/IfcOpenShell/IfcOpenShell/commit/322a2179e51a9ec4802fe9e35039c7e3e29f9678: Merges names with number suffix, as well (ex: foo, foo.001, foo.002)
This commit is contained in:
@@ -818,7 +818,12 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.merge_identical_objects"
|
||||
bl_label = "Merge Identical Objects"
|
||||
bl_description = "Merge identical IFC entities (that match all attributes). Hold Shift to merge by name/identification attribute only"
|
||||
bl_description = (
|
||||
"Merge identical IFC objects (that match all attributes).\n"
|
||||
"\n"
|
||||
"SHIFT + CLICK to merge by name/identification attribute only.\n"
|
||||
"Merges names with number suffix, as well (ex: foo, foo.001, foo.002)\n"
|
||||
)
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
object_type: bpy.props.EnumProperty( # pyright: ignore[reportRedeclaration]
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import bmesh
|
||||
import bpy
|
||||
@@ -145,10 +146,21 @@ class Debug(bonsai.core.tool.Debug):
|
||||
Args:
|
||||
object_type: The type of object to merge
|
||||
by_name_or_identification_only: If True, merge based only on Name attribute (or equivalent identifier).
|
||||
Strips .XXX suffix patterns (e.g., 'foo.001' matches 'foo', 'foo.002').
|
||||
For PERSON, uses Identification. For APPLICATION, uses ApplicationFullName.
|
||||
For PERSON_AND_ORGANIZATION, uses combination of person and organization identifiers.
|
||||
"""
|
||||
|
||||
def normalize_name(name: str) -> str:
|
||||
"""Remove .XXX suffix pattern from names (e.g., 'foo.001' -> 'foo')"""
|
||||
if not name:
|
||||
return ""
|
||||
# Match pattern: name ending with .digits
|
||||
match = re.match(r"^(.+)\.\d+$", name)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return name
|
||||
|
||||
def get_hash(element: ifcopenshell.entity_instance) -> int:
|
||||
data = element.get_info_2(include_identifier=False, recursive=True)
|
||||
if object_type == "APPLICATION":
|
||||
@@ -161,6 +173,30 @@ class Debug(bonsai.core.tool.Debug):
|
||||
|
||||
def get_name_key(element: ifcopenshell.entity_instance) -> str:
|
||||
"""Get key based on name/identifier attribute for the given object type"""
|
||||
if object_type == "STYLE":
|
||||
name = element.Name if element.Name else ""
|
||||
return normalize_name(name)
|
||||
elif object_type == "MATERIAL":
|
||||
name = element.Name if element.Name else ""
|
||||
return normalize_name(name)
|
||||
elif object_type == "ORGANIZATION":
|
||||
name = element.Name if element.Name else ""
|
||||
return normalize_name(name)
|
||||
elif object_type == "APPLICATION":
|
||||
name = element.ApplicationFullName if element.ApplicationFullName else ""
|
||||
return normalize_name(name)
|
||||
elif object_type == "PERSON":
|
||||
ident = element.Identification if element.Identification else ""
|
||||
return normalize_name(ident)
|
||||
elif object_type == "PERSON_AND_ORGANIZATION":
|
||||
person_id = element.ThePerson.Identification if element.ThePerson.Identification else ""
|
||||
org_name = element.TheOrganization.Name if element.TheOrganization.Name else ""
|
||||
return f"{normalize_name(person_id)}|{normalize_name(org_name)}"
|
||||
else:
|
||||
assert_never(object_type)
|
||||
|
||||
def get_element_name(element: ifcopenshell.entity_instance) -> str:
|
||||
"""Get the actual name/identifier from element for sorting purposes"""
|
||||
if object_type == "STYLE":
|
||||
return element.Name if element.Name else ""
|
||||
elif object_type == "MATERIAL":
|
||||
@@ -209,7 +245,7 @@ class Debug(bonsai.core.tool.Debug):
|
||||
hash_to_elements: defaultdict[Union[int, str], list[ifcopenshell.entity_instance]]
|
||||
|
||||
if by_name_or_identification_only:
|
||||
# Group by name/identifier only
|
||||
# Group by name/identifier only (with .XXX suffix normalization)
|
||||
hash_to_elements = defaultdict(list)
|
||||
for element in elements:
|
||||
name_key = get_name_key(element)
|
||||
@@ -217,6 +253,18 @@ class Debug(bonsai.core.tool.Debug):
|
||||
if not name_key:
|
||||
continue
|
||||
hash_to_elements[name_key].append(element)
|
||||
|
||||
# Sort elements within each group to keep the one without suffix (or lowest suffix)
|
||||
for name_key in hash_to_elements:
|
||||
# Sort by: 1) prefer names without .XXX suffix, 2) then by original name
|
||||
def sort_key(el):
|
||||
name = get_element_name(el)
|
||||
# Check if name has .XXX suffix
|
||||
has_suffix = bool(re.match(r"^.+\.\d+$", name))
|
||||
# Return tuple: (has_suffix, name) - sort by no suffix first, then alphabetically
|
||||
return (has_suffix, name)
|
||||
|
||||
hash_to_elements[name_key].sort(key=sort_key)
|
||||
else:
|
||||
# Group by full hash
|
||||
hash_to_elements = defaultdict(list)
|
||||
|
||||
Reference in New Issue
Block a user