bim.merge_identical_objects to support merging IfcMaterials

This commit is contained in:
Andrej730
2024-10-03 17:09:02 +05:00
parent 944e9c45ac
commit 7b0a792ff7
4 changed files with 51 additions and 18 deletions
+11 -9
View File
@@ -691,6 +691,7 @@ 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 = "For materials currently only IfcMaterials are supported"
bl_options = {"REGISTER", "UNDO"}
object_type: bpy.props.EnumProperty(
@@ -704,20 +705,21 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
)
def _execute(self, context):
object_type = self.object_type
if object_type == "STYLE":
merged_data = tool.Debug.merge_identical_objects("style")
if merged_data:
print("Merged styles:")
for style_type, style_names in merged_data.items():
print(f"- {style_type}: {', '.join(style_names)}")
merged = sum(len(v) for v in merged_data.values())
object_type: str = self.object_type
if object_type in ("STYLE", "MATERIAL"):
merged_data = tool.Debug.merge_identical_objects(object_type)
else:
self.report({"ERROR"}, f"Invalid object type {object_type}.")
return {"CANCELLED"}
plural_object_type = f"{object_type.lower()}s"
if merged_data:
print(f"Merged {plural_object_type}:")
for element_type, element_names in merged_data.items():
print(f"- {element_type}: {', '.join(element_names)}")
merged = sum(len(v) for v in merged_data.values())
msg = " See system console for details." if merged else ""
self.report({"INFO"}, f"{merged} identical {object_type.lower()}s were merged.{msg}")
self.report({"INFO"}, f"{merged} identical {plural_object_type} were merged.{msg}")
if merged == 0:
return
+4 -1
View File
@@ -524,7 +524,10 @@ class BIM_PT_purge(Panel):
layout = self.layout
layout.operator("bim.purge_unused_objects", text="Purge Unused Profiles").object_type = "PROFILE"
layout.operator("bim.purge_unused_objects", text="Purge Unused Types").object_type = "TYPE"
layout.operator("bim.purge_unused_objects", text="Purge Unused Materials").object_type = "MATERIAL"
row = layout.row(align=True)
row.label(text="Materials: ")
row.operator("bim.purge_unused_objects", text="Purge Unused").object_type = "MATERIAL"
row.operator("bim.merge_identical_objects", text="Merge Identical").object_type = "MATERIAL"
row = layout.row(align=True)
row.label(text="Styles: ")
row.operator("bim.purge_unused_objects", text="Purge Unused").object_type = "STYLE"
+18 -7
View File
@@ -19,6 +19,7 @@
import os
import json
import bpy
import ifcopenshell.api.material
import ifcopenshell.express
import ifcopenshell.express.schema
import ifcopenshell.express.schema_class
@@ -104,7 +105,7 @@ class Debug(bonsai.core.tool.Debug):
return sum(unused.values())
@classmethod
def merge_identical_objects(cls, object_type: Literal["style"]) -> dict[str, list[str]]:
def merge_identical_objects(cls, object_type: Literal["STYLE", "MATERIAL"]) -> dict[str, list[str]]:
"""Merge identical objects.
Note that Styles UI (or other UI) should be updated manually after using this method.
@@ -115,31 +116,35 @@ class Debug(bonsai.core.tool.Debug):
return hash(json.dumps(element.get_info(include_identifier=False, recursive=True), sort_keys=True))
ifc_file = tool.Ifc.get()
merged_element_types: dict[str, list[str]] = {}
if object_type == "style":
if object_type == "STYLE":
declaration = tool.Ifc.schema().declaration_by_name("IfcPresentationStyle")
merged_element_types: dict[str, list[str]] = {}
element_types = [e.name() for e in ifcopenshell.util.schema.get_subtypes(declaration)]
elif object_type == "MATERIAL":
# TODO: support other material types.
element_types = ["IfcMaterial"]
for element_type in element_types:
elements = ifc_file.by_type(element_type)
elements = ifc_file.by_type(element_type, include_subtypes=False)
# Calculate hashes.
hash_to_elements: defaultdict[int, list[ifcopenshell.entity_instance]] = defaultdict(list)
for element in elements:
# Ignore unnamed styles as they may be not safe to merge.
# Ignore unnamed elements as they may be not safe to merge.
if not element.Name:
continue
element_hash = get_hash(element)
hash_to_elements[element_hash].append(element)
merged_elements_names: list[str] = []
# Merge styles.
# Merge elements.
for elements in hash_to_elements.values():
if len(elements) == 1:
continue
main_element = elements[0]
if object_type == "style":
if object_type == "STYLE":
main_style_obj = tool.Ifc.get_object(main_element)
for style in elements[1:]:
ifcopenshell.util.element.replace_element(style, main_element)
@@ -151,6 +156,12 @@ class Debug(bonsai.core.tool.Debug):
merged_elements_names.append(style.Name)
bonsai.core.style.remove_style(tool.Ifc, tool.Style, style, reload_styles_ui=False)
elif object_type == "MATERIAL":
for material in elements[1:]:
ifcopenshell.util.element.replace_element(material, main_element)
merged_elements_names.append(material.Name)
ifcopenshell.api.material.remove_material(ifc_file, material)
if merged_elements_names:
merged_element_types[element_type] = merged_elements_names
return merged_element_types
+18 -1
View File
@@ -19,6 +19,7 @@
import os
import bpy
import ifcopenshell
import ifcopenshell.api.style
import ifcopenshell.util.schema
import bonsai.core.tool
import bonsai.tool as tool
@@ -85,5 +86,21 @@ class TestMergeIdenticalObject(NewFile):
for style in ifc.by_type(style_type):
style.FillStyles = (ifc.create_entity("IfcFillAreaStyleHatching"),)
merge_data = subject.merge_identical_objects("style")
merge_data = subject.merge_identical_objects("STYLE")
assert merge_data == {style_type: [style_type] for style_type in style_types}
def test_merge_identical_materials(self):
tool.Ifc.set(ifc := ifcopenshell.file())
context = ifc.create_entity("IfcGeometricRepresentationContext")
style = ifc.create_entity("IfcSurfaceStyle")
element_types = ["IfcMaterial"]
for element_type in element_types:
ifc.create_entity(element_type, Name=element_type, Category="Category")
ifc.create_entity(element_type, Name=element_type, Category="Category")
ifc.create_entity(element_type, Name="NotToMerge", Category="Category")
for material in ifc.by_type(element_type):
ifcopenshell.api.style.assign_material_style(ifc, material, style, context)
merge_data = subject.merge_identical_objects("MATERIAL")
assert merge_data == {element_type: [element_type] for element_type in element_types}