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): class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.merge_identical_objects" bl_idname = "bim.merge_identical_objects"
bl_label = "Merge Identical Objects" bl_label = "Merge Identical Objects"
bl_description = "For materials currently only IfcMaterials are supported"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
object_type: bpy.props.EnumProperty( object_type: bpy.props.EnumProperty(
@@ -704,20 +705,21 @@ class MergeIdenticalObjects(bpy.types.Operator, tool.Ifc.Operator):
) )
def _execute(self, context): def _execute(self, context):
object_type = self.object_type object_type: str = self.object_type
if object_type == "STYLE": if object_type in ("STYLE", "MATERIAL"):
merged_data = tool.Debug.merge_identical_objects("style") merged_data = tool.Debug.merge_identical_objects(object_type)
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())
else: else:
self.report({"ERROR"}, f"Invalid object type {object_type}.") self.report({"ERROR"}, f"Invalid object type {object_type}.")
return {"CANCELLED"} 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 "" 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: if merged == 0:
return return
+4 -1
View File
@@ -524,7 +524,10 @@ class BIM_PT_purge(Panel):
layout = self.layout 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 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 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 = layout.row(align=True)
row.label(text="Styles: ") row.label(text="Styles: ")
row.operator("bim.purge_unused_objects", text="Purge Unused").object_type = "STYLE" row.operator("bim.purge_unused_objects", text="Purge Unused").object_type = "STYLE"
+18 -7
View File
@@ -19,6 +19,7 @@
import os import os
import json import json
import bpy import bpy
import ifcopenshell.api.material
import ifcopenshell.express import ifcopenshell.express
import ifcopenshell.express.schema import ifcopenshell.express.schema
import ifcopenshell.express.schema_class import ifcopenshell.express.schema_class
@@ -104,7 +105,7 @@ class Debug(bonsai.core.tool.Debug):
return sum(unused.values()) return sum(unused.values())
@classmethod @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. """Merge identical objects.
Note that Styles UI (or other UI) should be updated manually after using this method. 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)) return hash(json.dumps(element.get_info(include_identifier=False, recursive=True), sort_keys=True))
ifc_file = tool.Ifc.get() 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") 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)] 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: 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. # Calculate hashes.
hash_to_elements: defaultdict[int, list[ifcopenshell.entity_instance]] = defaultdict(list) hash_to_elements: defaultdict[int, list[ifcopenshell.entity_instance]] = defaultdict(list)
for element in elements: 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: if not element.Name:
continue continue
element_hash = get_hash(element) element_hash = get_hash(element)
hash_to_elements[element_hash].append(element) hash_to_elements[element_hash].append(element)
merged_elements_names: list[str] = [] merged_elements_names: list[str] = []
# Merge styles. # Merge elements.
for elements in hash_to_elements.values(): for elements in hash_to_elements.values():
if len(elements) == 1: if len(elements) == 1:
continue continue
main_element = elements[0] main_element = elements[0]
if object_type == "style": if object_type == "STYLE":
main_style_obj = tool.Ifc.get_object(main_element) main_style_obj = tool.Ifc.get_object(main_element)
for style in elements[1:]: for style in elements[1:]:
ifcopenshell.util.element.replace_element(style, main_element) ifcopenshell.util.element.replace_element(style, main_element)
@@ -151,6 +156,12 @@ class Debug(bonsai.core.tool.Debug):
merged_elements_names.append(style.Name) merged_elements_names.append(style.Name)
bonsai.core.style.remove_style(tool.Ifc, tool.Style, style, reload_styles_ui=False) 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: if merged_elements_names:
merged_element_types[element_type] = merged_elements_names merged_element_types[element_type] = merged_elements_names
return merged_element_types return merged_element_types
+18 -1
View File
@@ -19,6 +19,7 @@
import os import os
import bpy import bpy
import ifcopenshell import ifcopenshell
import ifcopenshell.api.style
import ifcopenshell.util.schema import ifcopenshell.util.schema
import bonsai.core.tool import bonsai.core.tool
import bonsai.tool as tool import bonsai.tool as tool
@@ -85,5 +86,21 @@ class TestMergeIdenticalObject(NewFile):
for style in ifc.by_type(style_type): for style in ifc.by_type(style_type):
style.FillStyles = (ifc.create_entity("IfcFillAreaStyleHatching"),) 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} 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}