From 8c0b06b36e2a2ce2ecd5662ae7db5ec6b40f39be Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 2 Feb 2022 17:49:57 +1100 Subject: [PATCH] Different BSDFs are now recognised for different reflectance methods. --- src/blenderbim/blenderbim/core/style.py | 61 +++-- src/blenderbim/blenderbim/core/tool.py | 4 + src/blenderbim/blenderbim/tool/style.py | 146 ++++++++++- src/blenderbim/test/core/test_style.py | 105 +++++++- src/blenderbim/test/tool/test_style.py | 317 ++++++++++++++++++++++-- 5 files changed, 576 insertions(+), 57 deletions(-) diff --git a/src/blenderbim/blenderbim/core/style.py b/src/blenderbim/blenderbim/core/style.py index 0cc161c0be..f6295a9b67 100644 --- a/src/blenderbim/blenderbim/core/style.py +++ b/src/blenderbim/blenderbim/core/style.py @@ -20,12 +20,22 @@ def add_style(ifc, style, obj=None): element = ifc.run("style.add_style", name=style.get_name(obj)) ifc.link(element, obj) - ifc.run( - "style.add_surface_style", - style=element, - ifc_class="IfcSurfaceStyleRendering", - attributes=style.get_surface_rendering_attributes(obj), - ) + if style.can_support_rendering_style(obj): + attributes = style.get_surface_rendering_attributes(obj) + ifc.run("style.add_surface_style", style=element, ifc_class="IfcSurfaceStyleRendering", attributes=attributes) + else: + attributes = style.get_surface_shading_attributes(obj) + ifc.run("style.add_surface_style", style=element, ifc_class="IfcSurfaceStyleShading", attributes=attributes) + + if style.can_support_texture_style(obj): + textures = ifc.run("style.add_surface_textures", textures=style.get_surface_textures(obj)) + ifc.run( + "style.add_surface_style", + style=element, + ifc_class="IfcSurfaceStyleWithTextures", + attributes={"Textures": textures}, + ) + material = ifc.get_entity(obj) if material: ifc.run("style.assign_material_style", material=material, style=element, context=style.get_context(obj)) @@ -39,16 +49,39 @@ def remove_style(ifc, style, obj=None): def update_style_colours(ifc, style, obj=None): - rendering_style = style.get_surface_rendering_style(obj) - if rendering_style: - attributes = style.get_surface_rendering_attributes(obj) - ifc.run("style.edit_surface_style", style=rendering_style, attributes=attributes) - return + element = ifc.get_entity(obj) - shading_style = style.get_surface_shading_style(obj) - if shading_style: + if style.can_support_rendering_style(obj): + rendering_style = style.get_surface_rendering_style(obj) + attributes = style.get_surface_rendering_attributes(obj) + if rendering_style: + ifc.run("style.edit_surface_style", style=rendering_style, attributes=attributes) + else: + ifc.run( + "style.add_surface_style", style=element, ifc_class="IfcSurfaceStyleRendering", attributes=attributes + ) + else: + shading_style = style.get_surface_shading_style(obj) attributes = style.get_surface_shading_attributes(obj) - ifc.run("style.edit_surface_style", style=shading_style, attributes=attributes) + if shading_style: + ifc.run("style.edit_surface_style", style=shading_style, attributes=attributes) + else: + ifc.run("style.add_surface_style", style=element, ifc_class="IfcSurfaceStyleShading", attributes=attributes) + + texture_style = style.get_surface_texture_style(obj) + if style.can_support_texture_style(obj): + textures = ifc.run("style.add_surface_textures", textures=style.get_surface_textures(obj)) + if texture_style: + ifc.run("style.edit_surface_style", style=texture_style, attributes={"Textures": textures}) + else: + ifc.run( + "style.add_surface_style", + style=element, + ifc_class="IfcSurfaceStyleWithTextures", + attributes={"Textures": textures}, + ) + elif texture_style: + ifc.run("style.remove_style", style=texture_style) def unlink_style(ifc, style, obj=None): diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index e91b12bebf..964c0d8a65 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -360,6 +360,8 @@ class Structural: @interface class Style: + def can_support_rendering_style(cls, obj): pass + def can_support_texture_style(cls, obj): pass def disable_editing(cls, obj): pass def enable_editing(cls, obj): pass def export_surface_attributes(cls, obj): pass @@ -370,6 +372,8 @@ class Style: def get_surface_rendering_style(cls, obj): pass def get_surface_shading_attributes(cls, obj): pass def get_surface_shading_style(cls, obj): pass + def get_surface_texture_style(cls, obj): pass + def get_surface_textures(cls, obj): pass def import_surface_attributes(cls, style, obj): pass diff --git a/src/blenderbim/blenderbim/tool/style.py b/src/blenderbim/blenderbim/tool/style.py index 8706d8138c..9ea1647d17 100644 --- a/src/blenderbim/blenderbim/tool/style.py +++ b/src/blenderbim/blenderbim/tool/style.py @@ -23,6 +23,45 @@ import blenderbim.bim.helper class Style(blenderbim.core.tool.Style): + @classmethod + def can_support_rendering_style(cls, obj): + return obj.use_nodes and hasattr(obj.node_tree, "nodes") + + @classmethod + def can_support_texture_style(cls, obj): + if not obj.node_tree: + return False + + output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) + if not output: + return False + + # For now, we assume only a single BSDF is allowed in our node tree. + try: + bsdf = output.inputs["Surface"].links[0].from_node + except: + return False + + # For a quick check whether we have a compatible node tree, we check + # whether or not we have at least one image texture node that has a + # texture assigned and outputs to the bsdf. + textures = [n for n in obj.node_tree.nodes if n.type == "TEX_IMAGE" and n.image] + + def does_texture_connect_to(node, target): + if node == target: + return True + try: + output = node.outputs[0].links[0].to_node + return does_texture_connect_to(output, target) + except: + return False + + for texture in textures: + if does_texture_connect_to(texture, bsdf): + return True + + return False + @classmethod def disable_editing(cls, obj): obj.BIMStyleProperties.is_editing = False @@ -55,12 +94,8 @@ class Style(blenderbim.core.tool.Style): def get_surface_rendering_attributes(cls, obj): transparency = obj.diffuse_color[3] diffuse_color = obj.diffuse_color - if obj.use_nodes and hasattr(obj.node_tree, "nodes") and "Principled BSDF" in obj.node_tree.nodes: - bsdf = obj.node_tree.nodes["Principled BSDF"] - transparency = bsdf.inputs["Alpha"].default_value - diffuse_color = bsdf.inputs["Base Color"].default_value - transparency = 1 - transparency - return { + + attributes = { "SurfaceColour": { "Name": None, "Red": obj.diffuse_color[0], @@ -68,14 +103,50 @@ class Style(blenderbim.core.tool.Style): "Blue": obj.diffuse_color[2], }, "Transparency": transparency, - "DiffuseColour": { - "Name": None, - "Red": diffuse_color[0], - "Green": diffuse_color[1], - "Blue": diffuse_color[2], - }, } + bsdfs = {n.type: n for n in obj.node_tree.nodes} + if "BSDF_GLOSSY" in bsdfs: + attributes["ReflectanceMethod"] = "METAL" + bsdf = bsdfs["BSDF_GLOSSY"] + attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)} + diffuse_color = bsdf.inputs["Color"].default_value + elif "BSDF_DIFFUSE" in bsdfs: + attributes["ReflectanceMethod"] = "MATT" + bsdf = bsdfs["BSDF_DIFFUSE"] + attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)} + diffuse_color = bsdf.inputs["Color"].default_value + elif "BSDF_GLASS" in bsdfs: + attributes["ReflectanceMethod"] = "GLASS" + bsdf = bsdfs["BSDF_GLASS"] + attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)} + diffuse_color = bsdf.inputs["Color"].default_value + elif "EMISSION" in bsdfs: + attributes["ReflectanceMethod"] = "FLAT" + bsdf = bsdfs["EMISSION"] + attributes["SpecularHighlight"] = None + diffuse_color = bsdf.inputs["Color"].default_value + elif "BSDF_PRINCIPLED" in bsdfs: + attributes["ReflectanceMethod"] = "NOTDEFINED" + bsdf = bsdfs["BSDF_PRINCIPLED"] + attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)} + diffuse_color = bsdf.inputs["Base Color"].default_value + attributes["Transparency"] = 1 - bsdf.inputs["Alpha"].default_value + else: + attributes["ReflectanceMethod"] = "NOTDEFINED" + attributes["SpecularHighlight"] = None + attributes["DiffuseColour"] = attributes["SurfaceColour"] + return attributes + + attributes["DiffuseColour"] = { + "Name": None, + "Red": diffuse_color[0], + "Green": diffuse_color[1], + "Blue": diffuse_color[2], + } + + return attributes + @classmethod def get_surface_rendering_style(cls, obj): if obj.BIMMaterialProperties.ifc_style_id: @@ -107,6 +178,57 @@ class Style(blenderbim.core.tool.Style): if items: return items[0] + @classmethod + def get_surface_texture_style(cls, obj): + if obj.BIMMaterialProperties.ifc_style_id: + style = tool.Ifc.get().by_id(obj.BIMMaterialProperties.ifc_style_id) + items = [s for s in style.Styles if s.is_a("IfcSurfaceStyleWithTextures")] + if items: + return items[0] + + @classmethod + def get_surface_textures(cls, obj): + output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) + bsdf = output.inputs["Surface"].links[0].from_node + node_mappings = { + "BSDF_GLOSSY": { + "DIFFUSE": "Color", + "SHININESS": "Roughness", + "NORMAL": "Normal", + }, + "BSDF_DIFFUSE": { + "DIFFUSE": "Color", + "SHININESS": "Roughness", + "NORMAL": "Normal", + }, + "BSDF_GLASS": { + "DIFFUSE": "Color", + "SHININESS": "Roughness", + "NORMAL": "Normal", + }, + "EMISSION": { + "DIFFUSE": "Color", + }, + "BSDF_PRINCIPLED": { + "DIFFUSE": "Base Color", + "SHININESS": "Roughness", + "NORMAL": "Normal", + "SPECULAR": "Specular", + "SELFILLUMINATION": "Emission Strength", + "OPACITY": "Alpha", + }, + } + + maps = {} + if bsdf.type not in node_mappings: + return maps + + for map_type, input_name in node_mappings[bsdf.type].items(): + if bsdf.inputs[input_name].links: + maps[map_type] = bsdf.inputs[input_name].links[0].from_node + + return maps + @classmethod def import_surface_attributes(cls, style, obj): obj.BIMStyleProperties.attributes.clear() diff --git a/src/blenderbim/test/core/test_style.py b/src/blenderbim/test/core/test_style.py index 64dc5adea8..7ae606244f 100644 --- a/src/blenderbim/test/core/test_style.py +++ b/src/blenderbim/test/core/test_style.py @@ -21,28 +21,35 @@ from test.core.bootstrap import ifc, style class TestAddStyle: - def predict(self, ifc, style, obj="obj"): - style.get_name(obj).should_be_called().will_return("name") + def test_it_adds_a_style_with_rendering_attributes(self, ifc, style): + style.get_name("obj").should_be_called().will_return("name") ifc.run("style.add_style", name="name").should_be_called().will_return("style") - ifc.link("style", obj).should_be_called() - style.get_surface_rendering_attributes(obj).should_be_called().will_return("attributes") + ifc.link("style", "obj").should_be_called() + + style.can_support_rendering_style("obj").should_be_called().will_return(True) + style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes") ifc.run( "style.add_surface_style", style="style", ifc_class="IfcSurfaceStyleRendering", attributes="attributes" ).should_be_called() - ifc.get_entity(obj).should_be_called().will_return(None) - def test_it_adds_a_style_with_rendering_attributes(self, ifc, style): - self.predict(ifc, style) + style.can_support_texture_style("obj").should_be_called().will_return(False) + + ifc.get_entity("obj").should_be_called().will_return(None) assert subject.add_style(ifc, style, obj="obj") == "style" def test_adding_a_style_linked_to_a_material(self, ifc, style): style.get_name("obj").should_be_called().will_return("name") ifc.run("style.add_style", name="name").should_be_called().will_return("style") ifc.link("style", "obj").should_be_called() - style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes") + + style.can_support_rendering_style("obj").should_be_called().will_return(False) + style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") ifc.run( - "style.add_surface_style", style="style", ifc_class="IfcSurfaceStyleRendering", attributes="attributes" + "style.add_surface_style", style="style", ifc_class="IfcSurfaceStyleShading", attributes="attributes" ).should_be_called() + + style.can_support_texture_style("obj").should_be_called().will_return(False) + ifc.get_entity("obj").should_be_called().will_return("material") style.get_context("obj").should_be_called().will_return("context") ifc.run("style.assign_material_style", material="material", style="style", context="context").should_be_called() @@ -58,17 +65,93 @@ class TestRemoveStyle: class TestUpdateStyleColours: - def test_updating_rendering_colours_if_available(self, ifc, style): + def test_updating_rendering_style_if_available(self, ifc, style): + ifc.get_entity("obj").should_be_called().will_return("element") + style.can_support_rendering_style("obj").should_be_called().will_return(True) style.get_surface_rendering_style("obj").should_be_called().will_return("style") style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes") ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() + style.get_surface_texture_style("obj").should_be_called().will_return(None) + style.can_support_texture_style("obj").should_be_called().will_return(False) subject.update_style_colours(ifc, style, obj="obj") - def test_updating_shading_colours_as_a_fallback_if_available(self, ifc, style): + def test_adding_a_rendering_style_if_not_available(self, ifc, style): + ifc.get_entity("obj").should_be_called().will_return("element") + style.can_support_rendering_style("obj").should_be_called().will_return(True) style.get_surface_rendering_style("obj").should_be_called().will_return(None) + style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes") + ifc.run( + "style.add_surface_style", style="element", ifc_class="IfcSurfaceStyleRendering", attributes="attributes" + ).should_be_called() + style.get_surface_texture_style("obj").should_be_called().will_return(None) + style.can_support_texture_style("obj").should_be_called().will_return(False) + subject.update_style_colours(ifc, style, obj="obj") + + def test_updating_shading_style_as_a_fallback_if_available(self, ifc, style): + ifc.get_entity("obj").should_be_called().will_return("element") + style.can_support_rendering_style("obj").should_be_called().will_return(False) style.get_surface_shading_style("obj").should_be_called().will_return("style") style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() + style.get_surface_texture_style("obj").should_be_called().will_return(None) + style.can_support_texture_style("obj").should_be_called().will_return(False) + subject.update_style_colours(ifc, style, obj="obj") + + def test_adding_a_shading_style_as_a_fallback_if_not_available(self, ifc, style): + ifc.get_entity("obj").should_be_called().will_return("element") + style.can_support_rendering_style("obj").should_be_called().will_return(False) + style.get_surface_shading_style("obj").should_be_called().will_return(None) + style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") + ifc.run( + "style.add_surface_style", style="element", ifc_class="IfcSurfaceStyleShading", attributes="attributes" + ).should_be_called() + style.get_surface_texture_style("obj").should_be_called().will_return(None) + style.can_support_texture_style("obj").should_be_called().will_return(False) + subject.update_style_colours(ifc, style, obj="obj") + + def test_updating_texture_style_if_available(self, ifc, style): + ifc.get_entity("obj").should_be_called().will_return("element") + style.can_support_rendering_style("obj").should_be_called().will_return(False) + style.get_surface_shading_style("obj").should_be_called().will_return("style") + style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") + ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() + + style.get_surface_texture_style("obj").should_be_called().will_return("style") + style.can_support_texture_style("obj").should_be_called().will_return(True) + style.get_surface_textures("obj").should_be_called().will_return("textures") + ifc.run("style.add_surface_textures", textures="textures").should_be_called().will_return("ifc_textures") + ifc.run("style.edit_surface_style", style="style", attributes={"Textures": "ifc_textures"}).should_be_called() + subject.update_style_colours(ifc, style, obj="obj") + + def test_adding_texture_style_if_not_available(self, ifc, style): + ifc.get_entity("obj").should_be_called().will_return("element") + style.can_support_rendering_style("obj").should_be_called().will_return(False) + style.get_surface_shading_style("obj").should_be_called().will_return("style") + style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") + ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() + + style.get_surface_texture_style("obj").should_be_called().will_return(None) + style.can_support_texture_style("obj").should_be_called().will_return(True) + style.get_surface_textures("obj").should_be_called().will_return("textures") + ifc.run("style.add_surface_textures", textures="textures").should_be_called().will_return("ifc_textures") + ifc.run( + "style.add_surface_style", + style="element", + ifc_class="IfcSurfaceStyleWithTextures", + attributes={"Textures": "ifc_textures"}, + ).should_be_called() + subject.update_style_colours(ifc, style, obj="obj") + + def test_removing_a_texture_style_if_no_longer_available(self, ifc, style): + ifc.get_entity("obj").should_be_called().will_return("element") + style.can_support_rendering_style("obj").should_be_called().will_return(False) + style.get_surface_shading_style("obj").should_be_called().will_return("style") + style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") + ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() + + style.get_surface_texture_style("obj").should_be_called().will_return("style") + style.can_support_texture_style("obj").should_be_called().will_return(False) + ifc.run("style.remove_style", style="style").should_be_called() subject.update_style_colours(ifc, style, obj="obj") diff --git a/src/blenderbim/test/tool/test_style.py b/src/blenderbim/test/tool/test_style.py index f58a6c8ecf..2f6ab95890 100644 --- a/src/blenderbim/test/tool/test_style.py +++ b/src/blenderbim/test/tool/test_style.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 . +import os import bpy import ifcopenshell import blenderbim.core.tool @@ -29,6 +30,54 @@ class TestImplementsTool(NewFile): assert isinstance(subject(), blenderbim.core.tool.Style) +class TestCanSupportRenderingStyle(NewFile): + def test_anything_with_nodes_can_support_a_rendering_style(self): + obj = bpy.data.materials.new("Material") + obj.use_nodes = True + assert subject.can_support_rendering_style(obj) is True + + def test_without_nodes_we_do_not_support_rendering(self): + obj = bpy.data.materials.new("Material") + obj.use_nodes = False + assert subject.can_support_rendering_style(obj) is False + + +class TestCanSupportTextureStyle(NewFile): + def test_without_nodes_we_do_not_support_textures(self): + obj = bpy.data.materials.new("Material") + obj.use_nodes = False + assert subject.can_support_texture_style(obj) is False + + def test_we_need_at_least_one_image_connected_to_a_bsdf(self): + obj = bpy.data.materials.new("Material") + obj.use_nodes = True + bsdf = obj.node_tree.nodes["Principled BSDF"] + node = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + cwd = os.path.dirname(os.path.realpath(__file__)) + image_path = os.path.join(cwd, "..", "files", "image.jpg") + node.image = bpy.data.images.load(image_path) + obj.node_tree.links.new(bsdf.inputs["Base Color"], node.outputs["Color"]) + assert subject.can_support_texture_style(obj) is True + + def test_a_texture_without_an_image_filepath_is_not_supported(self): + obj = bpy.data.materials.new("Material") + obj.use_nodes = True + bsdf = obj.node_tree.nodes["Principled BSDF"] + node = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Base Color"], node.outputs["Color"]) + assert subject.can_support_texture_style(obj) is False + + def test_the_texture_needs_to_connect_to_the_bsdf(self): + obj = bpy.data.materials.new("Material") + obj.use_nodes = True + bsdf = obj.node_tree.nodes["Principled BSDF"] + node = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + cwd = os.path.dirname(os.path.realpath(__file__)) + image_path = os.path.join(cwd, "..", "files", "image.jpg") + node.image = bpy.data.images.load(image_path) + assert subject.can_support_texture_style(obj) is False + + class TestDisableEditing(NewFile): def test_run(self): obj = bpy.data.materials.new("Material") @@ -81,32 +130,14 @@ class TestGetStyle(NewFile): class TestGetSurfaceRenderingAttributes(NewFile): - def test_get_colours_from_a_basic_material(self): - obj = bpy.data.materials.new("Material") - obj.diffuse_color = [1, 1, 1, 1] - assert subject.get_surface_rendering_attributes(obj) == { - "SurfaceColour": { - "Name": None, - "Red": 1, - "Green": 1, - "Blue": 1, - }, - "Transparency": 0, - "DiffuseColour": { - "Name": None, - "Red": 1, - "Green": 1, - "Blue": 1, - }, - } - - def test_get_different_surface_and_diffuse_colours_from_a_node_based_material(self): + def test_get_different_surface_and_diffuse_colours_from_a_principled_bsdf(self): obj = bpy.data.materials.new("Material") obj.diffuse_color = [1, 1, 1, 1] obj.use_nodes = True node = obj.node_tree.nodes["Principled BSDF"] node.inputs["Alpha"].default_value = 0.8 node.inputs["Base Color"].default_value = [0.5, 0.5, 0.5, 0.5] + node.inputs["Roughness"].default_value = 0.2 assert subject.get_surface_rendering_attributes(obj) == { "SurfaceColour": { "Name": None, @@ -121,6 +152,141 @@ class TestGetSurfaceRenderingAttributes(NewFile): "Green": 0.5, "Blue": 0.5, }, + "SpecularHighlight": {"IfcSpecularRoughness": 0.2}, + "ReflectanceMethod": "NOTDEFINED", + } + + def test_get_rendering_styles_from_a_glossy_bsdf(self): + obj = bpy.data.materials.new("Material") + obj.diffuse_color = [1, 1, 1, 1] + obj.use_nodes = True + node = obj.node_tree.nodes["Principled BSDF"] + obj.node_tree.nodes.remove(node) + node = obj.node_tree.nodes.new(type="ShaderNodeBsdfGlossy") + node.inputs["Color"].default_value = [0.5, 0.5, 0.5, 0.5] + node.inputs["Roughness"].default_value = 0.2 + assert subject.get_surface_rendering_attributes(obj) == { + "SurfaceColour": { + "Name": None, + "Red": 1, + "Green": 1, + "Blue": 1, + }, + "Transparency": 1, + "DiffuseColour": { + "Name": None, + "Red": 0.5, + "Green": 0.5, + "Blue": 0.5, + }, + "SpecularHighlight": {"IfcSpecularRoughness": 0.2}, + "ReflectanceMethod": "METAL", + } + + def test_get_rendering_styles_from_a_diffuse_bsdf(self): + obj = bpy.data.materials.new("Material") + obj.diffuse_color = [1, 1, 1, 1] + obj.use_nodes = True + node = obj.node_tree.nodes["Principled BSDF"] + obj.node_tree.nodes.remove(node) + node = obj.node_tree.nodes.new(type="ShaderNodeBsdfDiffuse") + node.inputs["Color"].default_value = [0.5, 0.5, 0.5, 0.5] + node.inputs["Roughness"].default_value = 0.2 + assert subject.get_surface_rendering_attributes(obj) == { + "SurfaceColour": { + "Name": None, + "Red": 1, + "Green": 1, + "Blue": 1, + }, + "Transparency": 1, + "DiffuseColour": { + "Name": None, + "Red": 0.5, + "Green": 0.5, + "Blue": 0.5, + }, + "SpecularHighlight": {"IfcSpecularRoughness": 0.2}, + "ReflectanceMethod": "MATT", + } + + def test_get_rendering_styles_from_a_glass_bsdf(self): + obj = bpy.data.materials.new("Material") + obj.diffuse_color = [1, 1, 1, 1] + obj.use_nodes = True + node = obj.node_tree.nodes["Principled BSDF"] + obj.node_tree.nodes.remove(node) + node = obj.node_tree.nodes.new(type="ShaderNodeBsdfGlass") + node.inputs["Color"].default_value = [0.5, 0.5, 0.5, 0.5] + node.inputs["Roughness"].default_value = 0.2 + assert subject.get_surface_rendering_attributes(obj) == { + "SurfaceColour": { + "Name": None, + "Red": 1, + "Green": 1, + "Blue": 1, + }, + "Transparency": 1, + "DiffuseColour": { + "Name": None, + "Red": 0.5, + "Green": 0.5, + "Blue": 0.5, + }, + "SpecularHighlight": {"IfcSpecularRoughness": 0.2}, + "ReflectanceMethod": "GLASS", + } + + def test_get_rendering_styles_from_a_emission_bsdf(self): + obj = bpy.data.materials.new("Material") + obj.diffuse_color = [1, 1, 1, 1] + obj.use_nodes = True + node = obj.node_tree.nodes["Principled BSDF"] + obj.node_tree.nodes.remove(node) + node = obj.node_tree.nodes.new(type="ShaderNodeEmission") + node.inputs["Color"].default_value = [0.5, 0.5, 0.5, 0.5] + assert subject.get_surface_rendering_attributes(obj) == { + "SurfaceColour": { + "Name": None, + "Red": 1, + "Green": 1, + "Blue": 1, + }, + "Transparency": 1, + "DiffuseColour": { + "Name": None, + "Red": 0.5, + "Green": 0.5, + "Blue": 0.5, + }, + "SpecularHighlight": None, + "ReflectanceMethod": "FLAT", + } + + def test_other_unsupported_bsdfs_copy_the_rendering_style_from_the_shading_colours_as_a_fallback(self): + obj = bpy.data.materials.new("Material") + obj.diffuse_color = [1, 1, 1, 1] + obj.use_nodes = True + node = obj.node_tree.nodes["Principled BSDF"] + obj.node_tree.nodes.remove(node) + node = obj.node_tree.nodes.new(type="ShaderNodeVolumePrincipled") + node.inputs["Color"].default_value = [0.5, 0.5, 0.5, 0.5] + assert subject.get_surface_rendering_attributes(obj) == { + "SurfaceColour": { + "Name": None, + "Red": 1, + "Green": 1, + "Blue": 1, + }, + "Transparency": 1, + "DiffuseColour": { + "Name": None, + "Red": 1, + "Green": 1, + "Blue": 1, + }, + "SpecularHighlight": None, + "ReflectanceMethod": "NOTDEFINED", } @@ -173,6 +339,117 @@ class TestGetSurfaceShadingStyle(NewFile): assert subject.get_surface_shading_style(obj) == style_item +class TestGetSurfaceTextureStyle(NewFile): + def test_run(self): + tool.Ifc.set(ifcopenshell.file()) + style_item = tool.Ifc.get().createIfcSurfaceStyleWithTextures() + style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item]) + obj = bpy.data.materials.new("Material") + obj.BIMMaterialProperties.ifc_style_id = style.id() + assert subject.get_surface_texture_style(obj) == style_item + + +class TestGetSurfaceTextures(NewFile): + def test_get_the_leaf_node_of_each_map_in_a_glossy_bsdf(self): + obj = bpy.data.materials.new("Material") + obj.use_nodes = True + bsdf = obj.node_tree.nodes["Principled BSDF"] + + obj.node_tree.nodes.remove(bsdf) + bsdf = obj.node_tree.nodes.new(type="ShaderNodeBsdfGlossy") + output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) + obj.node_tree.links.new(output.inputs["Surface"], bsdf.outputs["BSDF"]) + + diffuse = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Color"], diffuse.outputs["Color"]) + shininess = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Roughness"], shininess.outputs["Color"]) + normal = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Normal"], normal.outputs["Color"]) + + assert subject.get_surface_textures(obj) == {"DIFFUSE": diffuse, "SHININESS": shininess, "NORMAL": normal} + + def test_get_the_leaf_node_of_each_map_in_a_diffuse_bsdf(self): + obj = bpy.data.materials.new("Material") + obj.use_nodes = True + bsdf = obj.node_tree.nodes["Principled BSDF"] + + obj.node_tree.nodes.remove(bsdf) + bsdf = obj.node_tree.nodes.new(type="ShaderNodeBsdfDiffuse") + output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) + obj.node_tree.links.new(output.inputs["Surface"], bsdf.outputs["BSDF"]) + + diffuse = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Color"], diffuse.outputs["Color"]) + shininess = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Roughness"], shininess.outputs["Color"]) + normal = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Normal"], normal.outputs["Color"]) + + assert subject.get_surface_textures(obj) == {"DIFFUSE": diffuse, "SHININESS": shininess, "NORMAL": normal} + + def test_get_the_leaf_node_of_each_map_in_a_glass_bsdf(self): + obj = bpy.data.materials.new("Material") + obj.use_nodes = True + bsdf = obj.node_tree.nodes["Principled BSDF"] + + obj.node_tree.nodes.remove(bsdf) + bsdf = obj.node_tree.nodes.new(type="ShaderNodeBsdfGlass") + output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) + obj.node_tree.links.new(output.inputs["Surface"], bsdf.outputs["BSDF"]) + + diffuse = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Color"], diffuse.outputs["Color"]) + shininess = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Roughness"], shininess.outputs["Color"]) + normal = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Normal"], normal.outputs["Color"]) + + assert subject.get_surface_textures(obj) == {"DIFFUSE": diffuse, "SHININESS": shininess, "NORMAL": normal} + + def test_get_the_leaf_node_of_each_map_in_a_emission_bsdf(self): + obj = bpy.data.materials.new("Material") + obj.use_nodes = True + bsdf = obj.node_tree.nodes["Principled BSDF"] + + obj.node_tree.nodes.remove(bsdf) + bsdf = obj.node_tree.nodes.new(type="ShaderNodeEmission") + output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) + obj.node_tree.links.new(output.inputs["Surface"], bsdf.outputs["Emission"]) + + diffuse = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Color"], diffuse.outputs["Color"]) + + assert subject.get_surface_textures(obj) == {"DIFFUSE": diffuse} + + def test_get_the_leaf_node_of_each_map_in_a_principled_bsdf(self): + obj = bpy.data.materials.new("Material") + obj.use_nodes = True + bsdf = obj.node_tree.nodes["Principled BSDF"] + + diffuse = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Base Color"], diffuse.outputs["Color"]) + shininess = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Roughness"], shininess.outputs["Color"]) + normal = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Normal"], normal.outputs["Color"]) + specular = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Specular"], specular.outputs["Color"]) + emission = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Emission Strength"], emission.outputs["Color"]) + opacity = obj.node_tree.nodes.new(type="ShaderNodeTexImage") + obj.node_tree.links.new(bsdf.inputs["Alpha"], opacity.outputs["Color"]) + + assert subject.get_surface_textures(obj) == { + "DIFFUSE": diffuse, + "SHININESS": shininess, + "NORMAL": normal, + "SPECULAR": specular, + "SELFILLUMINATION": emission, + "OPACITY": opacity, + } + + class TestImportSurfaceAttributes(NewFile): def test_run(self): ifc = ifcopenshell.file()