mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
fix styles update on material assignment/unassignment #4843
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import mathutils
|
||||
import numpy as np
|
||||
from functools import reduce
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
@@ -39,7 +40,7 @@ from mathutils import Vector, Matrix
|
||||
from bpy_extras.object_utils import AddObjectHelper
|
||||
from . import prop
|
||||
import json
|
||||
from typing import Any, Union
|
||||
from typing import Any, Union, Optional
|
||||
|
||||
|
||||
class EnableAddType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
@@ -513,88 +514,19 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
|
||||
|
||||
|
||||
def ensure_material_assigned(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
if usecase_path == "material.assign_material":
|
||||
if not settings.get("material", None):
|
||||
return
|
||||
elements = settings["products"]
|
||||
else:
|
||||
elements = []
|
||||
for rel in ifc_file.by_type("IfcRelAssociatesMaterial"):
|
||||
if rel.RelatingMaterial == settings["material"] or [
|
||||
e for e in ifc_file.traverse(rel.RelatingMaterial) if e == settings["material"]
|
||||
]:
|
||||
elements.extend(rel.RelatedObjects)
|
||||
elements = settings["products"]
|
||||
|
||||
update_blender_ifc_materials(elements)
|
||||
for element in elements[:]:
|
||||
if element.is_a("IfcElementType"):
|
||||
elements.extend(tool.Model.get_occurrences_without_material_override(element))
|
||||
|
||||
tool.Model.apply_ifc_material_changes(elements, assigned_material=settings["material"])
|
||||
|
||||
|
||||
def ensure_material_unassigned(usecase_path: str, ifc_file: ifcopenshell.file, settings: dict[str, Any]) -> None:
|
||||
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: bpy.types.Object = tool.Ifc.get_object(element)
|
||||
if not obj or not obj.data:
|
||||
continue
|
||||
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
|
||||
|
||||
# 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
|
||||
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)
|
||||
for element in elements[:]:
|
||||
if element.is_a("IfcElementType"):
|
||||
elements.extend(tool.Model.get_occurrences_without_material_override(element))
|
||||
tool.Model.apply_ifc_material_changes(elements)
|
||||
|
||||
@@ -128,8 +128,9 @@ def switch_representation(
|
||||
if not current_obj_data and geometry.is_text_literal(representation):
|
||||
return
|
||||
|
||||
has_openings = apply_openings and getattr(entity, "HasOpenings", None)
|
||||
if has_openings:
|
||||
use_immediate_repr = apply_openings and getattr(entity, "HasOpenings", None)
|
||||
use_immediate_repr = use_immediate_repr or geometry.has_material_style_override(entity)
|
||||
if use_immediate_repr:
|
||||
# if it has openings make sure to switch to element's mapped representation
|
||||
representation = geometry.unresolve_type_representation(representation, entity)
|
||||
else:
|
||||
@@ -145,7 +146,7 @@ def switch_representation(
|
||||
else:
|
||||
new_repr_data = old_repr_data
|
||||
|
||||
geometry.change_object_data(obj, new_repr_data, is_global=is_global and not has_openings)
|
||||
geometry.change_object_data(obj, new_repr_data, is_global=is_global and not use_immediate_repr)
|
||||
geometry.record_object_materials(obj)
|
||||
|
||||
# we assume that all the occurences and the type have the same representation context active
|
||||
|
||||
@@ -508,6 +508,18 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
return bool(obj.data.splines)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def has_material_style_override(cls, element: ifcopenshell.entity_instance) -> bool:
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
return False
|
||||
own_material = ifcopenshell.util.element.get_material(element, should_inherit=False)
|
||||
if own_material:
|
||||
inherited_style = cls.get_inherited_material_style(element)
|
||||
style = tool.Material.get_style(own_material) if own_material else None
|
||||
if inherited_style != style:
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def import_representation(cls, obj, representation, apply_openings=True):
|
||||
logger = logging.getLogger("ImportIFC")
|
||||
@@ -802,17 +814,29 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
meshes_to_objects = {(obj := tool.Ifc.get_object(element)).data: obj for element in elements}
|
||||
|
||||
for obj in meshes_to_objects.values():
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
cls._reload_representation(obj)
|
||||
|
||||
@classmethod
|
||||
def _reload_representation(cls, obj: bpy.types.Object) -> None:
|
||||
"""Reload representation only for this object.
|
||||
|
||||
Be careful as this method won't reload representation for related objects
|
||||
that use the same representation but have different meshes
|
||||
(e.g. because of the openings).
|
||||
In the most cases just use reload_representation
|
||||
as it will handle those complications by itself.
|
||||
"""
|
||||
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
|
||||
blenderbim.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=representation,
|
||||
should_reload=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
apply_openings=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def remove_representation_item(cls, representation_item):
|
||||
@@ -1010,3 +1034,64 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
if (result := obj.BIMObjectProperties.blender_offset_type) == "NONE":
|
||||
result = obj.BIMObjectProperties.blender_offset_type = "OBJECT_PLACEMENT"
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def has_geometry_without_styles(cls, mesh: bpy.types.Mesh) -> bool:
|
||||
"""Check if mesh has geometry without styles.
|
||||
|
||||
Detects geometry without styles based on how
|
||||
MaterialCreator works - will check if either
|
||||
mesh has no material slots or has an empty material slot.
|
||||
"""
|
||||
return not mesh.materials or any(m is None for m in mesh.materials)
|
||||
|
||||
@classmethod
|
||||
def get_representation_styles(
|
||||
cls, representation: ifcopenshell.entity_instance
|
||||
) -> set[ifcopenshell.entity_instance]:
|
||||
"""Return a set of styles assigned to the representation directly."""
|
||||
|
||||
styles = set()
|
||||
|
||||
# Get all stylable representation items.
|
||||
items = []
|
||||
for item in representation.Items:
|
||||
if item.is_a("IfcMappedItem"):
|
||||
items.extend(item.MappingSource.MappedRepresentation.Items)
|
||||
if item.is_a("IfcBooleanResult"):
|
||||
operand = item.FirstOperand
|
||||
while True:
|
||||
items.append(operand)
|
||||
if operand.is_a("IfcBooleanResult"):
|
||||
operand = operand.FirstOperand
|
||||
else:
|
||||
break
|
||||
items.append(item)
|
||||
|
||||
for item in items:
|
||||
if not item.StyledByItem:
|
||||
continue
|
||||
current_styles = list(item.StyledByItem[0].Styles)
|
||||
while current_styles:
|
||||
style = current_styles.pop()
|
||||
if style.is_a("IfcPresentationStyle"):
|
||||
styles.add(style)
|
||||
elif style.is_a("IfcPresentationStyleAssignment"):
|
||||
current_styles.extend(style.Styles)
|
||||
|
||||
return styles
|
||||
|
||||
@classmethod
|
||||
def get_inherited_material_style(
|
||||
cls, element: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
return
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if not element_type:
|
||||
return
|
||||
materials = ifcopenshell.util.element.get_materials(element_type)
|
||||
if not materials:
|
||||
return
|
||||
material_style = tool.Material.get_style(materials[0])
|
||||
return material_style
|
||||
|
||||
@@ -1255,3 +1255,144 @@ class Model(blenderbim.core.tool.Model):
|
||||
if fillings:
|
||||
with bpy.context.temp_override(selected_objects=list(fillings.values())):
|
||||
bpy.ops.bim.recalculate_fill()
|
||||
|
||||
@classmethod
|
||||
def apply_ifc_material_changes(
|
||||
cls,
|
||||
elements: list[ifcopenshell.entity_instance],
|
||||
assigned_material: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> None:
|
||||
"""Update mesh blender materials for provided elements after material assignment/unassignment.
|
||||
|
||||
`assigned_material` argument is there just to indicate whether we apply material changes
|
||||
after material assignment or material unassignment.
|
||||
"""
|
||||
|
||||
style_object = None
|
||||
if assigned_material:
|
||||
# NOTE: currently only IfcMaterials are supported
|
||||
# for anyone else we just switch representation.
|
||||
if not assigned_material.is_a("IfcMaterial"):
|
||||
tool.Geometry.reload_representation([tool.Ifc.get_object(e) for e in elements])
|
||||
return
|
||||
|
||||
# Since different elements can share meshes (e.g. occurrences without openings)
|
||||
# we need to make sure not to process them multiple times.
|
||||
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: bpy.types.Object = tool.Ifc.get_object(element)
|
||||
if not obj or not obj.data:
|
||||
continue
|
||||
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):
|
||||
# It's unsafe to make changes to the mesh
|
||||
# as it's used by objects unrelated to the current change.
|
||||
# E.g. material with a style was assigned to a particular occurrence
|
||||
# and this change shouldn't be applied to other occurrences and type.
|
||||
objs_to_reload = mesh_users.intersection(objects)
|
||||
for obj in objs_to_reload:
|
||||
tool.Geometry._reload_representation(obj)
|
||||
continue
|
||||
|
||||
obj = next(iter(mesh_users))
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
own_material = ifcopenshell.util.element.get_material(element, should_inherit=False)
|
||||
inherited_mstyle = tool.Geometry.get_inherited_material_style(element)
|
||||
|
||||
if assigned_material:
|
||||
if own_material:
|
||||
ms2 = tool.Material.get_style(own_material)
|
||||
ms1 = inherited_mstyle
|
||||
else:
|
||||
ms1 = None
|
||||
ms2 = inherited_mstyle
|
||||
|
||||
cls.replace_material_style(mesh, obj, ms1, ms2)
|
||||
|
||||
else: # Material unnassignment.
|
||||
if not tool.Geometry.has_geometry_without_styles(mesh):
|
||||
tool.Geometry._reload_representation(obj)
|
||||
continue
|
||||
|
||||
if not inherited_mstyle:
|
||||
continue
|
||||
|
||||
cls.replace_material_style(mesh, obj, None, inherited_mstyle)
|
||||
|
||||
@classmethod
|
||||
def get_occurrences_without_material_override(
|
||||
cls, element_type: ifcopenshell.entity_instance
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
occurrences = [
|
||||
e
|
||||
for e in ifcopenshell.util.element.get_types(element_type)
|
||||
if not ifcopenshell.util.element.get_material(e, should_inherit=False)
|
||||
]
|
||||
return occurrences
|
||||
|
||||
@classmethod
|
||||
def replace_material_style(
|
||||
cls,
|
||||
mesh: bpy.types.Mesh,
|
||||
obj: bpy.types.Object,
|
||||
mstyle1: Union[ifcopenshell.entity_instance, None],
|
||||
mstyle2: Union[ifcopenshell.entity_instance, None],
|
||||
) -> None:
|
||||
if mstyle1 == mstyle2:
|
||||
return
|
||||
|
||||
# Get Blender materials.
|
||||
mbstyle1, mbstyle2 = None, None
|
||||
if mstyle1:
|
||||
mbstyle1 = tool.Ifc.get_object(mstyle1)
|
||||
assert isinstance(mbstyle1, bpy.types.Material)
|
||||
if mstyle2:
|
||||
mbstyle2 = tool.Ifc.get_object(mstyle2)
|
||||
assert isinstance(mbstyle2, bpy.types.Material)
|
||||
|
||||
# Copy data to the list as mesh.materials doesn't allow to search for None.
|
||||
materials: list[Union[bpy.types.Material, None]] = mesh.materials[:]
|
||||
# Material style is overridden by representation item, nothing to change.
|
||||
if mesh.materials and mbstyle1 not in materials:
|
||||
return
|
||||
|
||||
i1 = None
|
||||
if mbstyle1 is None:
|
||||
if not materials:
|
||||
mesh.materials.append(None)
|
||||
i1 = 0
|
||||
else:
|
||||
i1 = materials.index(None)
|
||||
else:
|
||||
rep = tool.Geometry.get_active_representation(obj)
|
||||
assert rep # Type checker.
|
||||
rep_styles = tool.Geometry.get_representation_styles(rep)
|
||||
if mbstyle1 in rep_styles:
|
||||
tool.Geometry._reload_representation(obj)
|
||||
return
|
||||
else:
|
||||
i1 = materials.index(mbstyle1)
|
||||
|
||||
if mbstyle2 in materials:
|
||||
i2 = materials.index(mbstyle2)
|
||||
# Reassign faces.
|
||||
buffer = np.empty(len(mesh.polygons), dtype=np.int32)
|
||||
mesh.polygons.foreach_get("material_index", buffer)
|
||||
buffer[buffer == i1] = i2
|
||||
mesh.polygons.foreach_set("material_index", buffer)
|
||||
|
||||
mesh.materials.pop(index=i1)
|
||||
else:
|
||||
mesh.materials[i1] = mbstyle2
|
||||
|
||||
@@ -18,13 +18,18 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.material
|
||||
import ifcopenshell.api.style
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
import blenderbim.core.tool
|
||||
import blenderbim.tool as tool
|
||||
import numpy as np
|
||||
import json
|
||||
from test.bim.bootstrap import NewFile
|
||||
from blenderbim.tool.model import Model as subject
|
||||
from ifcopenshell.util.shape_builder import V
|
||||
from ifcopenshell.util.shape_builder import V, ShapeBuilder
|
||||
|
||||
|
||||
class TestImplementsTool(NewFile):
|
||||
@@ -438,3 +443,212 @@ class TestUsingArrays(NewFile):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
pset = ifcopenshell.util.element.get_pset(element, "BBIM_Array")
|
||||
assert pset is None, (obj, pset)
|
||||
|
||||
|
||||
class TestApplyIfcMaterialChanges(NewFile):
|
||||
def get_used_styles(self, obj: bpy.types.Object) -> set[ifcopenshell.entity_instance]:
|
||||
ifc_file = tool.Ifc.get()
|
||||
return {ifc_file.by_id(s.material.BIMMaterialProperties.ifc_style_id) for s in obj.material_slots if s.material}
|
||||
|
||||
def get_mesh(self, obj: bpy.types.Object) -> bpy.types.Mesh:
|
||||
mesh = obj.data
|
||||
assert isinstance(mesh, bpy.types.Mesh)
|
||||
return mesh
|
||||
|
||||
def setup_test(self, and_elements: bool = True) -> None:
|
||||
bpy.context.scene.BIMProjectProperties.template_file = "0"
|
||||
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
|
||||
bpy.ops.bim.create_project()
|
||||
ifc_file = tool.Ifc.get()
|
||||
|
||||
# Setup materials and styles.
|
||||
context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
assert context # Type checker.
|
||||
|
||||
red_material = ifcopenshell.api.material.add_material(ifc_file, "Red Material")
|
||||
bpy.ops.bim.load_styles(style_type="IfcSurfaceStyle")
|
||||
bpy.ops.bim.enable_adding_presentation_style()
|
||||
bpy.data.scenes["Scene"].BIMStylesProperties.style_name = "Red"
|
||||
bpy.ops.bim.add_presentation_style()
|
||||
red_style = next((i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Red"))
|
||||
ifcopenshell.api.style.assign_material_style(ifc_file, red_material, red_style, context)
|
||||
|
||||
blue_material = ifcopenshell.api.material.add_material(ifc_file, "Blue Material")
|
||||
bpy.ops.bim.enable_adding_presentation_style()
|
||||
bpy.data.scenes["Scene"].BIMStylesProperties.style_name = "Blue"
|
||||
bpy.ops.bim.add_presentation_style()
|
||||
blue_style = next((i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Blue"))
|
||||
ifcopenshell.api.style.assign_material_style(ifc_file, blue_material, blue_style, context)
|
||||
|
||||
bpy.ops.bim.enable_adding_presentation_style()
|
||||
bpy.data.scenes["Scene"].BIMStylesProperties.style_name = "Green"
|
||||
bpy.ops.bim.add_presentation_style()
|
||||
|
||||
if and_elements:
|
||||
self.setup_elements()
|
||||
|
||||
def setup_elements(self) -> None:
|
||||
ifc_file = tool.Ifc.get()
|
||||
blue_material = next((i for i in ifc_file.by_type("IfcMaterial") if i.Name == "Blue Material"))
|
||||
blue_style = tool.Material.get_style(blue_material)
|
||||
|
||||
# Element type.
|
||||
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
||||
element_type_obj = bpy.data.objects["Cube"]
|
||||
bpy.ops.bim.assign_class(ifc_class="IfcActuatorType", predefined_type="ELECTRICACTUATOR", userdefined_type="")
|
||||
element_type = tool.Ifc.get_entity(element_type_obj)
|
||||
|
||||
# Setup occurrences.
|
||||
relating_type_id = element_type.id()
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=relating_type_id)
|
||||
simple = bpy.context.active_object
|
||||
simple.name = "Simple"
|
||||
|
||||
# Occurrence with an opening.
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=relating_type_id)
|
||||
with_opening = bpy.context.active_object
|
||||
with_opening.name = "With Opening"
|
||||
bpy.ops.bim.add_potential_opening()
|
||||
tool.Blender.set_objects_selection(
|
||||
bpy.context, active_object=with_opening, selected_objects=[with_opening, bpy.data.objects["Opening"]]
|
||||
)
|
||||
bpy.ops.bim.add_opening()
|
||||
|
||||
# Occurrence with a material override.
|
||||
bpy.ops.bim.add_constr_type_instance(relating_type_id=relating_type_id)
|
||||
with_material = bpy.context.active_object
|
||||
with_material.name = "With Material"
|
||||
tool.Blender.set_objects_selection(bpy.context, active_object=with_material, selected_objects=[with_material])
|
||||
|
||||
ifcopenshell.api.material.assign_material(
|
||||
ifc_file, products=[tool.Ifc.get_entity(with_material)], material=blue_material
|
||||
)
|
||||
|
||||
assert self.get_used_styles(element_type_obj) == set()
|
||||
for element in ifc_file.by_type("IfcActuator"):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
expected = {blue_style} if obj.name == "With Material" else set()
|
||||
assert self.get_used_styles(obj) == expected
|
||||
|
||||
def test_element_type_and_occurrences(self):
|
||||
self.setup_test()
|
||||
ifc_file = tool.Ifc.get()
|
||||
element_type = next(ifc_file.by_type("IfcActuatorType").__iter__())
|
||||
red_material = next((i for i in ifc_file.by_type("IfcMaterial") if i.Name == "Red Material"))
|
||||
red_style = tool.Material.get_style(red_material)
|
||||
blue_style = next((i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Blue"))
|
||||
|
||||
ifcopenshell.api.material.assign_material(ifc_file, material=red_material, products=[element_type])
|
||||
assert self.get_used_styles(tool.Ifc.get_object(element_type)) == {red_style}
|
||||
for element in ifc_file.by_type("IfcActuator"):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
expected = {blue_style} if obj.name == "With Material" else {red_style}
|
||||
assert self.get_used_styles(obj) == expected
|
||||
|
||||
ifcopenshell.api.material.unassign_material(ifc_file, products=[element_type])
|
||||
assert self.get_used_styles(tool.Ifc.get_object(element_type)) == set()
|
||||
for element in ifc_file.by_type("IfcActuator"):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
expected = {blue_style} if obj.name == "With Material" else set()
|
||||
assert self.get_used_styles(obj) == expected
|
||||
|
||||
def test_dont_override_exisiting_styles(self):
|
||||
self.setup_test()
|
||||
ifc_file = tool.Ifc.get()
|
||||
element_type = next(ifc_file.by_type("IfcActuatorType").__iter__())
|
||||
red_material = next((i for i in ifc_file.by_type("IfcMaterial") if i.Name == "Red Material"))
|
||||
green_style = next((i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Green"))
|
||||
|
||||
# Occurrence with a style.
|
||||
element_type_obj = tool.Ifc.get_object(element_type)
|
||||
tool.Blender.set_objects_selection(
|
||||
bpy.context, active_object=element_type_obj, selected_objects=[element_type_obj]
|
||||
)
|
||||
bpy.ops.bim.assign_style_to_selected(style_id=green_style.id())
|
||||
|
||||
ifcopenshell.api.material.assign_material(ifc_file, material=red_material, products=[element_type])
|
||||
assert self.get_used_styles(tool.Ifc.get_object(element_type)) == {green_style}
|
||||
for element in ifc_file.by_type("IfcActuator"):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
assert self.get_used_styles(obj) == {green_style}
|
||||
|
||||
ifcopenshell.api.material.unassign_material(ifc_file, products=[element_type])
|
||||
assert self.get_used_styles(tool.Ifc.get_object(element_type)) == {green_style}
|
||||
for element in ifc_file.by_type("IfcActuator"):
|
||||
obj = tool.Ifc.get_object(element)
|
||||
assert self.get_used_styles(obj) == {green_style}
|
||||
|
||||
def test_assign_material_to_representation_that_has_2_items_and_1_item_has_a_style(self):
|
||||
self.setup_test(and_elements=False)
|
||||
ifc_file = tool.Ifc.get()
|
||||
red_material = next((i for i in ifc_file.by_type("IfcMaterial") if i.Name == "Red Material"))
|
||||
red_style = tool.Material.get_style(red_material)
|
||||
green_style = next((i for i in ifc_file.by_type("IfcSurfaceStyle") if i.Name == "Green"))
|
||||
|
||||
bpy.ops.mesh.primitive_cube_add(size=10, location=(0, 0, 4))
|
||||
obj = bpy.data.objects["Cube"]
|
||||
bpy.ops.bim.assign_class(ifc_class="IfcActuator", predefined_type="ELECTRICACTUATOR", userdefined_type="")
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
builder = ShapeBuilder(ifc_file)
|
||||
|
||||
# Change representation that consists of 2 rep items:
|
||||
# 1 with style and other without.
|
||||
rep = tool.Geometry.get_active_representation(obj)
|
||||
assert rep
|
||||
cube = rep.Items[0]
|
||||
cube2 = builder.deep_copy(cube)
|
||||
rep.Items = [cube, cube2]
|
||||
tool.Style.assign_style_to_representation_item(cube, green_style)
|
||||
tool.Geometry._reload_representation(obj)
|
||||
|
||||
def get_material_indices(mesh: bpy.types.Mesh) -> np.ndarray:
|
||||
buffer = np.empty(len(mesh.polygons), dtype=np.int32)
|
||||
mesh.polygons.foreach_get("material_index", buffer)
|
||||
return buffer
|
||||
|
||||
mesh = self.get_mesh(obj)
|
||||
assert len(mesh.materials) == 2
|
||||
assert set(mesh.materials) == {bpy.data.materials["Green"], None}
|
||||
|
||||
ifcopenshell.api.material.assign_material(ifc_file, products=[element], material=red_material)
|
||||
assert self.get_used_styles(obj) == {green_style, red_style}
|
||||
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
||||
mesh = self.get_mesh(obj)
|
||||
assert len(mesh.materials) == 2
|
||||
assert set(mesh.materials) == {bpy.data.materials["Green"], None}
|
||||
|
||||
# Test that if style is the same it would just reuse it.
|
||||
tool.Style.assign_style_to_representation_item(cube, red_style)
|
||||
tool.Geometry._reload_representation(obj)
|
||||
mesh = self.get_mesh(obj)
|
||||
assert len(mesh.materials) == 2
|
||||
assert set(mesh.materials) == {bpy.data.materials["Red"], None}
|
||||
|
||||
ifcopenshell.api.material.assign_material(ifc_file, products=[element], material=red_material)
|
||||
assert mesh.materials[:] == [bpy.data.materials["Red"]]
|
||||
# All polygons are just reassigned to the existing material.
|
||||
assert set(get_material_indices(mesh)) == {mesh.materials.find("Red")}
|
||||
|
||||
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
||||
mesh = self.get_mesh(obj)
|
||||
assert len(mesh.materials) == 2
|
||||
assert set(mesh.materials) == {bpy.data.materials["Red"], None}
|
||||
assert set(get_material_indices(mesh)) == {0, 1}
|
||||
|
||||
def test_assign_unassign_overriding_occurrence_material(self):
|
||||
self.setup_test(and_elements=True)
|
||||
ifc_file = tool.Ifc.get()
|
||||
element_type = next(ifc_file.by_type("IfcActuatorType").__iter__())
|
||||
red_material = next((i for i in ifc_file.by_type("IfcMaterial") if i.Name == "Red Material"))
|
||||
no_style_material = ifcopenshell.api.material.add_material(ifc_file, "No Style")
|
||||
obj = bpy.data.objects["Simple"]
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
|
||||
ifcopenshell.api.material.assign_material(ifc_file, material=red_material, products=[element_type])
|
||||
|
||||
# Override type material.
|
||||
ifcopenshell.api.material.assign_material(ifc_file, material=no_style_material, products=[element])
|
||||
assert self.get_mesh(obj).materials[:] == []
|
||||
|
||||
ifcopenshell.api.material.unassign_material(ifc_file, products=[element])
|
||||
assert self.get_mesh(obj).materials[:] == [bpy.data.materials["Red"]]
|
||||
|
||||
Reference in New Issue
Block a user