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).
This commit is contained in:
Andrej730
2024-05-01 15:18:12 +05:00
parent 0f548eb93e
commit bef5d3cca5
@@ -39,7 +39,7 @@ from mathutils import Vector, Matrix
from bpy_extras.object_utils import AddObjectHelper from bpy_extras.object_utils import AddObjectHelper
from . import prop from . import prop
import json import json
from typing import Any from typing import Any, Union
class EnableAddType(bpy.types.Operator, tool.Ifc.Operator): 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"] elements = settings["products"]
if elements[0].is_a("IfcElementType"): if elements[0].is_a("IfcElementType"):
elements.extend(ifcopenshell.util.element.get_types(elements[0])) 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: 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: if not obj or not obj.data:
continue continue
element_material = ifcopenshell.util.element.get_material(element) objects.add(obj)
if element_material:
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 continue
to_remove = []
for i, slot in enumerate(obj.material_slots): # NOTE: we need `obj` as removing materials and appending them to `mesh.materials`
if not slot.material: # 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 continue
material = tool.Ifc.get_entity(slot.material) ifc_material = tool.Ifc.get_entity(material)
if material: # it's blender material for style, so ignore it
to_remove.append(i) if not ifc_material:
total_removed = 0 continue
for i in to_remove: if ifc_material == current_material:
obj.active_material_index = i - total_removed continue
with bpy.context.temp_override(object=obj): material_slot.material = current_material
bpy.ops.object.material_slot_remove() material_replaced = True
total_removed += 1
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)