simplify process of loading blender materials #4843

We no longer traverse through representations searching for IfcSurfaceStyles and just rely on geometry.materials.
Possible after #4886 since:
- create_shape is now considering inherited materials
- material.instance_id is not returning IfcMaterial's style id instead of IfcMaterial id.
This commit is contained in:
Andrej730
2024-06-17 15:37:28 +05:00
parent 5731c4036a
commit e329b18e33
+53 -87
View File
@@ -59,103 +59,72 @@ class MaterialCreator:
return return
if not self.mesh or self.mesh.name in self.parsed_meshes: if not self.mesh or self.mesh.name in self.parsed_meshes:
return return
self.parsed_meshes.add(self.mesh.name)
self.add_default_material(element)
if self.parse_representations(element):
self.assign_material_slots_to_faces()
tool.Geometry.record_object_materials(obj)
def add_default_material(self, element: ifcopenshell.entity_instance) -> None: # mesh["ios_materials"] can contain:
element_material = ifcopenshell.util.element.get_material(element) # - ifc style id if style assigned to the representation items directly
if not element_material: # or through material with a style;
return # - ifc material id if both true:
for material in [m for m in self.ifc_importer.file.traverse(element_material) if m.is_a("IfcMaterial")]: # - element has a material without a style;
if not material.HasRepresentation: # - there are parts of the geometry that has no other style assigned to them;
continue # - -1 in case if there is no material;
surface_style = [ # - 0 in case if there are default materials used.
s for s in self.ifc_importer.file.traverse(material.HasRepresentation[0]) if s.is_a("IfcSurfaceStyle") # Though 0 value will not occur as we don't use default materials in IfcImporter.
]
if surface_style: self.parsed_meshes.add(self.mesh.name)
self.mesh.materials.append(self.styles[surface_style[0].id()]) self.load_texture_maps()
return self.assign_material_slots_to_faces()
tool.Geometry.record_object_materials(obj)
def load_existing_materials(self) -> None: def load_existing_materials(self) -> None:
for material in bpy.data.materials: for material in bpy.data.materials:
if material.BIMMaterialProperties.ifc_style_id: if material.BIMMaterialProperties.ifc_style_id:
self.styles[material.BIMMaterialProperties.ifc_style_id] = material self.styles[material.BIMMaterialProperties.ifc_style_id] = material
def parse_representations(self, element: ifcopenshell.entity_instance) -> bool: def get_ifc_coordinate(self, material: bpy.types.Material) -> Union[ifcopenshell.entity_instance, None]:
"""Search for styles in in all `element`'s representation items """Get IfcTextureCoordinate"""
and adds them to `self.mesh.materials`.\n texture_style = tool.Style.get_texture_style(material)
Returns `True` if any styles were found and added, returns `False` otherwise.""" if not texture_style:
has_parsed = False return
if hasattr(element, "Representation"): for texture in texture_style.Textures or []:
for representation in element.Representation.Representations: if coords := getattr(texture, "IsMappedBy", None):
if self.parse_representation(representation): coords = coords[0]
has_parsed = True # IfcTextureCoordinateGenerator handled in the style shader graph
elif hasattr(element, "RepresentationMaps"): if coords.is_a("IfcIndexedTextureMap"):
for representation_map in element.RepresentationMaps: return coords
if not representation_map.MappedRepresentation: # TODO: support IfcTextureMap
has_parsed = True # Accommodate invalid IFC data from Revit if coords.is_a("IfcTextureMap"):
elif self.parse_representation(representation_map.MappedRepresentation): print(f"WARNING. IfcTextureMap texture coordinates is not supported.")
has_parsed = True
return has_parsed
def parse_representation(self, representation: ifcopenshell.entity_instance) -> bool:
has_parsed = False
representation_items = self.resolve_all_stylable_representation_items(representation)
for item in representation_items:
if self.parse_representation_item(item):
has_parsed = True
return has_parsed
def parse_representation_item(self, item: ifcopenshell.entity_instance) -> bool:
if not item.StyledByItem:
return False
style_ids = []
styles = list(item.StyledByItem[0].Styles)
while styles:
style = styles.pop()
if style.is_a("IfcSurfaceStyle"):
style_ids.append(style.id())
elif style.is_a("IfcPresentationStyleAssignment"):
styles.extend(style.Styles)
if not style_ids:
return False
for style_id in style_ids:
material = self.styles[style_id]
def get_ifc_coordinate(material: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
"""returns IfcTextureCoordinate"""
texture_style = tool.Style.get_texture_style(material)
if not texture_style:
return return
for texture in texture_style.Textures or []:
if coords := getattr(texture, "IsMappedBy", None):
coords = coords[0]
# IfcTextureCoordinateGenerator handled in the style shader graph
if coords.is_a("IfcIndexedTextureMap"):
return coords
# TODO: support IfcTextureMap
if coords.is_a("IfcTextureMap"):
print(f"WARNING. IfcTextureMap texture coordinates is not supported.")
return
if coords := get_ifc_coordinate(material): def load_texture_maps(self) -> None:
for style_or_material_id in self.mesh["ios_materials"]:
if not (material := self.styles.get(style_or_material_id)):
continue
material = self.styles[style_or_material_id]
if coords := self.get_ifc_coordinate(material):
tool.Loader.load_indexed_texture_map(coords, self.mesh) tool.Loader.load_indexed_texture_map(coords, self.mesh)
if self.mesh.materials.find(material.name) == -1:
self.mesh.materials.append(material)
return True
def assign_material_slots_to_faces(self) -> None: def assign_material_slots_to_faces(self) -> None:
if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]: if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]:
return return
# Cannot return here if len(ios_materials) == 1 because ios_materials = self.mesh["ios_materials"]
# even with 1 style it may be not assigned to the entire geometry if len(ios_materials) == 1:
# as some faces need another empty material slot. style_or_material_id = ios_materials[0]
material_to_slot: dict[int, int] = {} # Has no styles / has just a material without a style.
if not (material := self.styles.get(style_or_material_id)):
return
# Has a style and it's assigned to the entire geometry.
# Otherwise we'll need to proceed as faces without styles
# will require an empty material slot.
if -1 not in self.mesh["ios_material_ids"]:
self.mesh.materials.append(material)
return
# Mapping of ios_materials indices
# to blender material slots indices.
material_to_slot: dict[int, int] = {}
empty_slot_index = None empty_slot_index = None
def get_empty_slot_index() -> int: def get_empty_slot_index() -> int:
@@ -167,17 +136,14 @@ class MaterialCreator:
# TODO: When they are not equal? # TODO: When they are not equal?
if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]): if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]):
for i, style_or_material_id in enumerate(self.mesh["ios_materials"]): for i, style_or_material_id in enumerate(ios_materials):
# ios_materials will contain not ifc style but ifc material id if both true:
# - there are parts of the geometry that has no style assigned to them;
# - element has a material without a style.
# It can also contain -1 in case if there is no material.
material_without_style = style_or_material_id not in self.styles material_without_style = style_or_material_id not in self.styles
if material_without_style: if material_without_style:
slot_index = get_empty_slot_index() slot_index = get_empty_slot_index()
else: else:
blender_material = self.styles[style_or_material_id] blender_material = self.styles[style_or_material_id]
slot_index = self.mesh.materials.find(blender_material.name) self.mesh.materials.append(blender_material)
slot_index = len(self.mesh.materials) - 1
material_to_slot[i] = slot_index material_to_slot[i] = slot_index
if -1 in self.mesh["ios_material_ids"]: if -1 in self.mesh["ios_material_ids"]: