diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 6b3b825645..467aae6676 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +from __future__ import annotations import os import re import bpy @@ -38,7 +39,7 @@ from blenderbim.bim.module.drawing.prop import ANNOTATION_TYPES_DATA class MaterialCreator: - def __init__(self, ifc_import_settings, ifc_importer): + def __init__(self, ifc_import_settings: IfcImportSettings, ifc_importer: IfcImporter): self.mesh = None self.obj = None self.materials = {} @@ -125,9 +126,21 @@ class MaterialCreator: return for style_id in style_ids: material = self.styles[style_id] - ifc_coordinate_id = material.BIMMaterialProperties.ifc_coordinate_id - if ifc_coordinate_id != 0: - self.load_texture_map(tool.Ifc.get().by_id(ifc_coordinate_id)) + + def get_ifc_coordinate(style_id): + style = self.ifc_importer.file.by_id(style_id) + if coords := getattr(style, "IsMappedTo", 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(style_id): + self.load_texture_map(coords) if self.mesh.materials.find(material.name) == -1: self.mesh.materials.append(material) return True diff --git a/src/blenderbim/blenderbim/bim/module/style/prop.py b/src/blenderbim/blenderbim/bim/module/style/prop.py index c8af2e0bb9..d6a99096dd 100644 --- a/src/blenderbim/blenderbim/bim/module/style/prop.py +++ b/src/blenderbim/blenderbim/bim/module/style/prop.py @@ -162,6 +162,7 @@ def update_shading_style(self, context): # TODO: support more more methods +# based on https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcReflectanceMethodEnum.htm REFLECTANCE_METHODS = [ ("PHYSICAL", "PHYSICAL", ""), ("FLAT", "FLAT", ""), @@ -171,6 +172,12 @@ REFLECTANCE_METHODS = [ ("NOTDEFINED", "NOTDEFINED", ""), ] +UV_MODES = [ + ("UV", "UV", "Actual UV data presented on the geometry"), + ("Generated", "Generated", "Automatically-generated UV from the vertex positions of the mesh"), + ("Camera", "Camera", "UV from position coordinate in camera space"), +] + def update_shader_graph(self, context): if not self.update_graph: @@ -209,7 +216,6 @@ class BIMStyleProperties(PropertyGroup): update=update_shading_style, ) - # GLTF style properties update_graph: BoolProperty( name="Update Shade Graph on Prop Change", description="Update shader graph in real time\nas you update style properties", @@ -217,6 +223,16 @@ class BIMStyleProperties(PropertyGroup): get=update_graph_get, set=update_graph_set, ) + + uv_mode: EnumProperty( + name="UV Mode", + description="Type of UV used for the textures", + items=UV_MODES, + default="UV", + update=update_shader_graph, + ) + + # GLTF style properties reflectance_method: EnumProperty( name="Reflectance Method", description="Reflectance method to use for the material", @@ -247,6 +263,8 @@ class BIMStyleProperties(PropertyGroup): ) roughness: bpy.props.FloatProperty(name="Roughness", default=0.0, min=0.0, max=1.0, update=update_shader_graph) metallic: bpy.props.FloatProperty(name="Metallic", default=0.0, min=0.0, max=1.0, update=update_shader_graph) + + # texture paths normal_path: bpy.props.StringProperty( name="NormalMap", maxlen=1024, diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py index 345dfc55f8..9d1b54f7df 100644 --- a/src/blenderbim/blenderbim/bim/module/style/ui.py +++ b/src/blenderbim/blenderbim/bim/module/style/ui.py @@ -423,6 +423,7 @@ class BIM_PT_STYLE_GRAPH(Panel): if not bpy.data.filepath: layout.label(text="Save .blend file to keep relative paths", icon="ERROR") + layout.prop(props, "uv_mode") layout.label(text="Texture Maps:") for texture_type in texture_maps: add_texture_path(STYLE_TEXTURE_PROPS_MAP[texture_type]) diff --git a/src/blenderbim/blenderbim/bim/prop.py b/src/blenderbim/blenderbim/bim/prop.py index 2c187b510c..34e0085a4e 100644 --- a/src/blenderbim/blenderbim/bim/prop.py +++ b/src/blenderbim/blenderbim/bim/prop.py @@ -439,7 +439,6 @@ class BIMMaterialProperties(PropertyGroup): attributes: CollectionProperty(name="Attributes", type=Attribute) # In Blender, a material object can map to an IFC material, IFC surface style, or both ifc_style_id: IntProperty(name="IFC Style ID") - ifc_coordinate_id: IntProperty(name="IFC Coordinate ID") shading_checksum: StringProperty(name="Shading Checksum") @@ -466,6 +465,5 @@ class BIMFacet(PropertyGroup): comparison: StringProperty(name="Comparison") - class BIMFilterGroup(PropertyGroup): filters: CollectionProperty(type=BIMFacet, name="filters") diff --git a/src/blenderbim/blenderbim/tool/loader.py b/src/blenderbim/blenderbim/tool/loader.py index 5c2b67433b..19d6fbdfc2 100644 --- a/src/blenderbim/blenderbim/tool/loader.py +++ b/src/blenderbim/blenderbim/tool/loader.py @@ -131,6 +131,22 @@ class Loader(blenderbim.core.tool.Loader): surface_style["SpecularHighlight"] = None return surface_style + @classmethod + def surface_texture_to_dict(cls, surface_texture): + if isinstance(surface_texture, dict): + return surface_texture + mappings = surface_texture.IsMappedBy or [] + surface_texture = surface_texture.get_info() + uv_mode = None + if mappings: + coordinates = mappings[0] + if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD": + uv_mode = "Generated" + elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE": + uv_mode = "Camera" + surface_texture["uv_mode"] = uv_mode or "UV" + return surface_texture + @classmethod def create_surface_style_rendering(cls, blender_material, surface_style): surface_style = cls.surface_style_to_dict(surface_style) @@ -192,12 +208,16 @@ class Loader(blenderbim.core.tool.Loader): @classmethod def create_surface_style_with_textures(cls, blender_material, rendering_style, texture_style): """supposed to be called after `create_surface_style_rendering`""" - if not isinstance(texture_style, list): - textures = [t.get_info() for t in texture_style.Textures] + if not isinstance(texture_style, list): # assume it's IfcSurfaceStyleWithTextures + textures = [cls.surface_texture_to_dict(t) for t in texture_style.Textures] else: textures = texture_style rendering_style = cls.surface_style_to_dict(rendering_style) + # `rendering_style` is a dict and `textures` is a list of dicts + # containing ifc data, that way method can be called by just providing those dictionaries + # without actually changing IFC data + reflectance_method = rendering_style["ReflectanceMethod"] if reflectance_method not in ("PHYSICAL", "NOTDEFINED", "FLAT"): print(f'WARNING. Unsupported reflectance method "{reflectance_method}" on style {rendering_style}') @@ -229,6 +249,16 @@ class Loader(blenderbim.core.tool.Loader): if reflectance_method in ["PHYSICAL", "NOTDEFINED"]: bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED") + + SUPPORTED_PBR_TEXTURES = ("NORMAL", "EMISSIVE", "METALLICROUGHNESS", "OCCLUSION", "DIFFUSE") + if mode not in SUPPORTED_PBR_TEXTURES: + print( + f"WARNING. Texture with {mode} Mode is not supported for style with PHYSICAL reflectance method.\n" + f"Supported types are: {', '.join(SUPPORTED_PBR_TEXTURES)}\n" + f"Texture by path {image_url} will be skipped." + ) + continue + if mode == "NORMAL": # add normal map node normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap") @@ -317,6 +347,10 @@ class Loader(blenderbim.core.tool.Loader): elif reflectance_method == "FLAT": bsdf = tool.Blender.get_material_node(blender_material, "MIX_SHADER") if mode != "EMISSIVE": + print( + "WARNING. Only EMISSIVE Mode textures are supported for style with FLAT reflectance method.\n" + f"{mode} Mode texture by path {image_url} will be skipped." + ) continue # remove RGB node from `create_surface_style_rendering` @@ -334,22 +368,15 @@ class Loader(blenderbim.core.tool.Loader): # extend the image by repeating pixels on its edges if RepeatS or RepeatT is False repeat_s = texture.get("RepeatS", True) repeat_t = texture.get("RepeatT", True) - if node and (not repeat_s or not repeat_t): + if not repeat_s or not repeat_t: node.extension = "EXTEND" - # TODO: add support for texture data not ifc elements # IsMappedBy could only get with the entity_instance for IFC4/IFC4x3 - if isinstance(texture, dict): - texture = tool.Ifc.get().by_id(texture['id']) - if node and getattr(texture, "IsMappedBy", None): - coordinates = texture.IsMappedBy[0] - coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord") - coord.location = node.location - Vector((200, 0)) - if coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD": - blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"]) - elif coordinates.is_a("IfcTextureCoordinateGenerator") and coordinates.Mode == "COORD-EYE": - blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"]) - else: - blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"]) - # save the TextureMap id for set uv when set material to mesh - blender_material.BIMMaterialProperties.ifc_coordinate_id = coordinates.id() + coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord") + coord.location = node.location - Vector((200, 0)) + if texture["uv_mode"] == "Generated": + blender_material.node_tree.links.new(coord.outputs["Generated"], node.inputs["Vector"]) + elif texture["uv_mode"] == "Camera": + blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"]) + else: # uv_mode == UV + blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"]) diff --git a/src/blenderbim/blenderbim/tool/style.py b/src/blenderbim/blenderbim/tool/style.py index a7279dce18..cb8f25c9c4 100644 --- a/src/blenderbim/blenderbim/tool/style.py +++ b/src/blenderbim/blenderbim/tool/style.py @@ -147,6 +147,7 @@ class Style(blenderbim.core.tool.Style): "Mode": prop_mode, "type": "IfcImageTexture", "URLReference": tool.Blender.blender_path_to_posix(path), + "uv_mode": props.uv_mode, } textures.append(texture_data) @@ -184,13 +185,17 @@ class Style(blenderbim.core.tool.Style): unused_texture_maps = list(STYLE_TEXTURE_PROPS_MAP.keys()) if texture_style: + uv_mode = None for texture in texture_style.Textures: + texture_data = tool.Loader.surface_texture_to_dict(texture) + uv_mode = texture_data["uv_mode"] if texture.Mode not in texture_maps: print(f"WARNING. Unsupported texture mode: {texture.Mode}. Supported maps: {texture_maps}") continue prop_blender = STYLE_TEXTURE_PROPS_MAP.get(texture.Mode, None) setattr(props, prop_blender, texture.URLReference) unused_texture_maps.remove(texture.Mode) + props.uv_mode = uv_mode if uv_mode else "UV" # clear empty texture fields for texture_mode in unused_texture_maps: