From 03a6ee9121a66458667978fc4facf2fd685d3d3d Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 24 Nov 2023 15:26:48 +0500 Subject: [PATCH] BBIM Shader Graph Settings - setting uv mode for the textures Now you can set UV mode that will be used to sample textures (previously it was always using UV data on geometry or breaking if it was otherwise). Basically now you can use autogenerated UV for the objects without UV. https://i.imgur.com/WtYdpmU.png Removed BIMMaterialProperties.ifc_coordinate_id as it was only used during the ifc_import and maintained by the `Loader.create_surface_style_with_textures` which is also used to update live shader graph that not always have specific IFC id for the UV. --- src/blenderbim/blenderbim/bim/import_ifc.py | 21 +++++-- .../blenderbim/bim/module/style/prop.py | 20 +++++- .../blenderbim/bim/module/style/ui.py | 1 + src/blenderbim/blenderbim/bim/prop.py | 2 - src/blenderbim/blenderbim/tool/loader.py | 63 +++++++++++++------ src/blenderbim/blenderbim/tool/style.py | 5 ++ 6 files changed, 87 insertions(+), 25 deletions(-) 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: