From bef5d3cca514db9d7346e15b15357fa1445606bb Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 1 May 2024 15:18:12 +0500 Subject: [PATCH] blenderbim - fix bug with unassigning material When you would unassign material (e.g. implicitly by removing the object), it might have also removed materials slots from other occurrences of the same type. Which may had some unexpected sideffects later on - as unassigned styles saving IFC project (BBIM unassigned styles based on a assumption that it was the user decision to remove the materials and related styles). --- .../blenderbim/bim/module/model/product.py | 78 +++++++++++++++---- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index 5da0e79283..7fb235cd2c 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -39,7 +39,7 @@ from mathutils import Vector, Matrix from bpy_extras.object_utils import AddObjectHelper from . import prop import json -from typing import Any +from typing import Any, Union class EnableAddType(bpy.types.Operator, tool.Ifc.Operator): @@ -555,23 +555,69 @@ def ensure_material_unassigned(usecase_path: str, ifc_file: ifcopenshell.file, s elements = settings["products"] if elements[0].is_a("IfcElementType"): elements.extend(ifcopenshell.util.element.get_types(elements[0])) + update_blender_ifc_materials(elements) + + +def update_blender_ifc_materials(elements: list[ifcopenshell.entity_instance]) -> None: + """update mesh blender materials that have ifc material connected to them + by replacing them with `blender_material`""" + # since different elements can share meshes (e.g. occurrecnes without openings) + # we need to make sure not to affect them accidentally + meshes_users: dict[bpy.types.Mesh, set[bpy.types.Object]] = dict() + for obj in bpy.data.objects: + if not obj.data: + continue + meshes_users.setdefault(obj.data, set()).add(obj) + + objects: set[bpy.types.Object] = set() for element in elements: - obj = tool.Ifc.get_object(element) + obj: bpy.types.Object = tool.Ifc.get_object(element) if not obj or not obj.data: continue - element_material = ifcopenshell.util.element.get_material(element) - if element_material: + objects.add(obj) + + meshes: set[bpy.types.Mesh] = {obj.data for obj in objects} + + for mesh in meshes: + mesh_users = meshes_users[mesh] + if not mesh_users.issubset(objects): continue - to_remove = [] - for i, slot in enumerate(obj.material_slots): - if not slot.material: + + # NOTE: we need `obj` as removing materials and appending them to `mesh.materials` + # will mess up mesh faces material indices + + # NOTE: we make an assumption here that all mesh users + # have the same material - they either inherit it from the type + # or type doesn't have a material. + # + # If we add option to UI to add materials overriding type materials + # then this assumption won't be safe anymore + + obj = next(iter(mesh_users)) + element = tool.Ifc.get_entity(obj) + current_material = ifcopenshell.util.element.get_material(element) + if current_material: + current_material = tool.Ifc.get_object(current_material) + + material_replaced = False + + for material_slot in obj.material_slots: + material = material_slot.material + if material is None: continue - material = tool.Ifc.get_entity(slot.material) - if material: - to_remove.append(i) - total_removed = 0 - for i in to_remove: - obj.active_material_index = i - total_removed - with bpy.context.temp_override(object=obj): - bpy.ops.object.material_slot_remove() - total_removed += 1 + ifc_material = tool.Ifc.get_entity(material) + # it's blender material for style, so ignore it + if not ifc_material: + continue + if ifc_material == current_material: + continue + material_slot.material = current_material + material_replaced = True + + if not material_replaced and current_material: + mesh.materials.append(current_material) + + # clear empty slots + for i, material in reversed(list(enumerate(mesh.materials[:]))): + if material is None: + mesh.materials.pop(index=i)