From 2dc9dc2000f06b618e0417178ffb76c90cb4b417 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 23 Jun 2023 18:04:32 +0500 Subject: [PATCH] Generating IFC compatible shader graphs Added new panel. in shader graph N-panel that allows you to create fully GLTF and IFC compatible shader graphs by just tweaking parameters from the panel. Note that it's still saved to IFC only when you hit "Save Current Style", before that it's still just stored in blender. https://imgur.com/a/vmkoLPH The premise is that if you want to have non-external style you'd be able to create it entirely from this panel - so there will no need to guess or to remember which parameters from the graph are supported by IFC and which are not. It's still work in progress, still need to polish it, add support for more reflectance methods and add suport for textures (need to be able to import/export it with .ifc and set them from this panel). --- .../blenderbim/bim/module/style/__init__.py | 1 + .../blenderbim/bim/module/style/operator.py | 19 +- .../blenderbim/bim/module/style/prop.py | 71 +++++++ .../blenderbim/bim/module/style/ui.py | 68 +++++-- src/blenderbim/blenderbim/bim/operator.py | 7 +- src/blenderbim/blenderbim/core/style.py | 16 +- src/blenderbim/blenderbim/tool/blender.py | 11 +- src/blenderbim/blenderbim/tool/loader.py | 162 ++++++++++------ src/blenderbim/blenderbim/tool/style.py | 182 ++++++++++++++++-- 9 files changed, 430 insertions(+), 107 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/style/__init__.py b/src/blenderbim/blenderbim/bim/module/style/__init__.py index 0d9c3901be..45a8d3364f 100644 --- a/src/blenderbim/blenderbim/bim/module/style/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/style/__init__.py @@ -45,6 +45,7 @@ classes = ( ui.BIM_PT_style_attributes, ui.BIM_PT_external_style_attributes, ui.BIM_UL_styles, + ui.BIM_PT_STYLE_GRAPH, ) diff --git a/src/blenderbim/blenderbim/bim/module/style/operator.py b/src/blenderbim/blenderbim/bim/module/style/operator.py index 06d055b61d..97519569dc 100644 --- a/src/blenderbim/blenderbim/bim/module/style/operator.py +++ b/src/blenderbim/blenderbim/bim/module/style/operator.py @@ -30,11 +30,26 @@ import os class UpdateStyleColours(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.update_style_colours" bl_label = "Save Current Shading Style" - bl_description = "Save current style values to IfcSurfaceStyleShading" + bl_description = ( + "Save current style values to IfcSurfaceStyleShading.\n\n" + "ALT+CLICK to see saved values details" + ) bl_options = {"REGISTER", "UNDO"} + verbose: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + + def invoke(self, context, event): + # verobse print to console on alt+click + # make sure to use SKIP_SAVE on property, otherwise it might get stuck + if event.type == "LEFTMOUSE" and event.alt: + self.verbose = True + return self.execute(context) + def _execute(self, context): - core.update_style_colours(tool.Ifc, tool.Style, obj=context.active_object.active_material) + mat = context.active_object.active_material + core.update_style_colours(tool.Ifc, tool.Style, obj=mat, verbose=self.verbose) + if self.verbose: + self.report({"INFO"}, "Check the system console to see saved style properties") + tool.Style.set_surface_style_props(mat) class UpdateStyleTextures(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/style/prop.py b/src/blenderbim/blenderbim/bim/module/style/prop.py index 1b2a9035be..21d86530f4 100644 --- a/src/blenderbim/blenderbim/bim/module/style/prop.py +++ b/src/blenderbim/blenderbim/bim/module/style/prop.py @@ -96,9 +96,41 @@ def update_shading_style(self, context): if rendering_style and texture_style: tool.Loader.create_surface_style_with_textures(blender_material, rendering_style, texture_style) + tool.Style.set_surface_style_props(blender_material) tool.Style.record_shading(blender_material) +# TODO: support more more methods and also textures +REFLECTANCE_METHODS = [ + ("PHYSICAL", "PHYSICAL", ""), + ("FLAT", "FLAT", ""), + # ("METAL", "METAL", ""), + # ("MATT", "MATT", ""), + # ("GLASS", "GLASS", ""), + # ("NOTDEFINED", "NOTDEFINED", ""), +] + + +def update_shader_graph(self, context): + if not self.update_graph: + return + + material = self.id_data + style_data = tool.Style.get_surface_style_from_props(material) + tool.Loader.create_surface_style_rendering(material, style_data) + + +def update_graph_get(self): + return self.get("update_graph", True) + + +def update_graph_set(self, value): + self["update_graph"] = value + if value: + material = self.id_data + tool.Style.set_surface_style_props(material) + + class BIMStyleProperties(PropertyGroup): attributes: CollectionProperty(name="Attributes", type=Attribute) is_editing: BoolProperty(name="Is Editing") @@ -113,3 +145,42 @@ class BIMStyleProperties(PropertyGroup): default="Shading", 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", + default=True, + get=update_graph_get, + set=update_graph_set, + ) + reflectance_method: EnumProperty( + name="Reflectance Method", + description="Reflectance method to use for the material", + items=REFLECTANCE_METHODS, + default="PHYSICAL", + update=update_shader_graph, + ) + surface_color: bpy.props.FloatVectorProperty( + name="Surface Color", + subtype="COLOR", + default=(1, 1, 1, 1), + min=0.0, + max=1.0, + size=4, + update=update_shader_graph, + ) + diffuse_color: bpy.props.FloatVectorProperty( + name="Diffuse Color", + subtype="COLOR", + default=(1, 1, 1, 1), + min=0.0, + max=1.0, + size=4, + update=update_shader_graph, + ) + transparency: bpy.props.FloatProperty( + name="Transparency", default=0.0, min=0.0, max=1.0, update=update_shader_graph + ) + 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) diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py index 33a75aa778..d21098034a 100644 --- a/src/blenderbim/blenderbim/bim/module/style/ui.py +++ b/src/blenderbim/blenderbim/bim/module/style/ui.py @@ -65,6 +65,24 @@ class BIM_PT_styles(Panel): self.layout.template_list("BIM_UL_styles", "", self.props, "styles", self.props, "active_style_index") +def draw_style_ui(self, context): + mat = context.material + props = mat.BIMMaterialProperties + style_props = mat.BIMStyleProperties + row = self.layout.row(align=True) + if not props.ifc_style_id: + row.operator("bim.add_style", icon="ADD") + return + + row.prop(style_props, "active_style_type", icon="SHADING_RENDERED", text="") + row.operator("bim.update_current_style", icon="FILE_REFRESH", text="") + row = self.layout.row(align=True) + row.operator("bim.update_style_colours", icon="GREASEPENCIL") + row.operator("bim.update_style_textures", icon="TEXTURE", text="") + row.operator("bim.unlink_style", icon="UNLINKED", text="") + row.operator("bim.remove_style", icon="X", text="").style = props.ifc_style_id + + class BIM_PT_style(MaterialButtonsPanel, Panel): bl_label = "IFC Style" bl_idname = "BIM_PT_style" @@ -81,22 +99,11 @@ class BIM_PT_style(MaterialButtonsPanel, Panel): ) def draw(self, context): - props = context.active_object.active_material.BIMMaterialProperties - style_props = context.active_object.active_material.BIMStyleProperties mat = context.material - row = self.layout.row(align=True) + props = mat.BIMMaterialProperties + draw_style_ui(self, context) if not props.ifc_style_id: - row.operator("bim.add_style", icon="ADD") return - - row.prop(style_props, "active_style_type", icon="SHADING_RENDERED", text="") - row.operator("bim.update_current_style", icon="FILE_REFRESH", text="") - row = self.layout.row(align=True) - row.operator("bim.update_style_colours", icon="GREASEPENCIL") - row.operator("bim.update_style_textures", icon="TEXTURE", text="") - row.operator("bim.unlink_style", icon="UNLINKED", text="") - row.operator("bim.remove_style", icon="X", text="").style = props.ifc_style_id - row = self.layout.row(align=True) row.prop(mat, "diffuse_color", text="Viewport Color" if mat.use_nodes else "Render Color") @@ -212,3 +219,38 @@ class BIM_UL_styles(UIList): row2 = row.row() row2.alignment = "RIGHT" row2.label(text=str(item.total_elements)) + + +class BIM_PT_STYLE_GRAPH(Panel): + bl_idname = "BIM_PT_style_graph" + bl_space_type = "NODE_EDITOR" + bl_label = "IFC Style Graph Settings" + bl_region_type = "UI" + bl_category = "BBIM" + + def draw(self, context): + layout = self.layout + props = context.active_object.active_material.BIMStyleProperties + + draw_style_ui(self, context) + layout.separator() + box = layout.box() + box.label(text="Creating shader from this panel") + box.label(text="ensures that shader is") + box.label(text="GLTF compatible") + box.label(text="and therefore will be ") + box.label(text="stored in IFC safely.") + layout.separator() + + layout.prop(props, "update_graph", text="Graph Auto Update") + layout.label(text="Reflectance Method:") + layout.prop(props, "reflectance_method", text="") + layout.prop(props, "surface_color") + layout.prop(props, "transparency") + layout.prop(props, "diffuse_color") + + if props.reflectance_method == "PHYSICAL": + layout.prop(props, "metallic") + + if props.reflectance_method not in ("FLAT", "NOTDEFINED"): + layout.prop(props, "roughness") diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index 7c3eb67426..c8300531fa 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -491,7 +491,7 @@ class BIM_OT_add_section_plane(bpy.types.Operator): continue material.blend_method = "HASHED" material.shadow_method = "HASHED" - material_output = self.get_node(material.node_tree.nodes, "OUTPUT_MATERIAL") + material_output = tool.Blender.get_material_node(material, "OUTPUT_MATERIAL", {"is_active_output": True}) if not material_output: continue from_socket = material_output.inputs[0].links[0].from_socket @@ -501,11 +501,6 @@ class BIM_OT_add_section_plane(bpy.types.Operator): material.node_tree.links.new(from_socket, section_override.inputs[0]) material.node_tree.links.new(section_override.outputs[0], material_output.inputs[0]) - def get_node(self, nodes, node_type): - for node in nodes: - if node.type == node_type: - return node - class BIM_OT_remove_section_plane(bpy.types.Operator): """Remove selected section plane. No effect if executed on a regular object""" diff --git a/src/blenderbim/blenderbim/core/style.py b/src/blenderbim/blenderbim/core/style.py index b00d437fff..c2a720a28d 100644 --- a/src/blenderbim/blenderbim/core/style.py +++ b/src/blenderbim/blenderbim/core/style.py @@ -54,18 +54,28 @@ def remove_style(ifc, material, style_tool, style=None): style_tool.import_presentation_styles(style_tool.get_active_style_type()) -def update_style_colours(ifc, style, obj=None): +def update_style_colours(ifc, style, obj=None, verbose=False): element = style.get_style(obj) if style.can_support_rendering_style(obj): - rendering_style = style.get_surface_rendering_style(obj) - attributes = style.get_surface_rendering_attributes(obj) + style_elements = style.get_style_elements(obj) + rendering_style = style_elements.get("IfcSurfaceStyleRendering", None) + texture_style = style_elements.get("IfcSurfaceStyleWithTextures", None) + attributes = style.get_surface_rendering_attributes(obj, verbose) 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 ) + + # # TODO: uncomment to support saving textures + # # TODO: uvs? + # textures = ifc.run("style.add_surface_textures", material=obj) + # if not texture_style and textures: + # texture_style = ifc.get().createIfcSurfaceStyleWithTextures() + # if texture_style: + # texture_style.Textures = textures else: shading_style = style.get_surface_shading_style(obj) attributes = style.get_surface_shading_attributes(obj) diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py index 1f2cb6536f..eb2683acf0 100644 --- a/src/blenderbim/blenderbim/tool/blender.py +++ b/src/blenderbim/blenderbim/tool/blender.py @@ -107,6 +107,13 @@ class Blender: return False return False + @classmethod + def show_error_message(cls, text): + """useful for showing error messages outside blender operators""" + def error(self, context): + self.layout.label(text=text) + bpy.context.window_manager.popup_menu(error, title="Error", icon="ERROR") + @classmethod def get_viewport_context(cls): """Get viewport area context for context overriding. @@ -170,13 +177,13 @@ class Blender: shader_editor.pin = previous_pin_setting @classmethod - def get_material_node(cls, blender_material, node_type): + def get_material_node(cls, blender_material, node_type, kwargs={}): """returns first node from the `blender_material` shader graph with type `node_type`""" if not blender_material.use_nodes: return nodes = blender_material.node_tree.nodes for node in nodes: - if node.type == node_type: + if node.type == node_type and all(getattr(node, a) == kwargs[a] for a in kwargs): return node @classmethod diff --git a/src/blenderbim/blenderbim/tool/loader.py b/src/blenderbim/blenderbim/tool/loader.py index 72473b58f4..8099a24439 100644 --- a/src/blenderbim/blenderbim/tool/loader.py +++ b/src/blenderbim/blenderbim/tool/loader.py @@ -22,7 +22,7 @@ import ifcopenshell.util.element import blenderbim.core.tool import blenderbim.tool as tool import os -import mathutils +from mathutils import Vector # Progressively we'll refactor loading elements into Blender objects into this @@ -58,16 +58,12 @@ class Loader(blenderbim.core.tool.Loader): @classmethod def create_surface_style_shading(cls, blender_material, surface_style): + surface_style = cls.surface_style_to_dict(surface_style) alpha = 1.0 # Transparency was added in IFC4 - if hasattr(surface_style, "Transparency") and surface_style.Transparency: - alpha = 1 - surface_style.Transparency - blender_material.diffuse_color = ( - surface_style.SurfaceColour.Red, - surface_style.SurfaceColour.Green, - surface_style.SurfaceColour.Blue, - alpha, - ) + if transparency := surface_style.get("Transparency", None): + alpha = 1 - transparency + blender_material.diffuse_color = surface_style["SurfaceColour"][:3] + (alpha,) @classmethod def restart_material_node_tree(cls, blender_material): @@ -76,44 +72,75 @@ class Loader(blenderbim.core.tool.Loader): for n in nodes[:]: nodes.remove(n) output = nodes.new("ShaderNodeOutputMaterial") + output.location = Vector((300, 300)) bsdf = nodes.new("ShaderNodeBsdfPrincipled") + bsdf.location = Vector((10, 300)) links.new(bsdf.outputs["BSDF"], output.inputs["Surface"]) + @classmethod + def surface_style_to_dict(cls, surface_style): + if isinstance(surface_style, dict): + return surface_style + surface_style = surface_style.get_info() + color_to_tuple = lambda x: (x.Red, x.Green, x.Blue, 1) + + if surface_style["SurfaceColour"]: + surface_style["SurfaceColour"] = color_to_tuple(surface_style["SurfaceColour"]) + + if surface_style["DiffuseColour"] and surface_style["DiffuseColour"].is_a("IfcColourRgb"): + surface_style["DiffuseColour"] = ("IfcColourRgb", color_to_tuple(surface_style["DiffuseColour"])) + + elif surface_style["DiffuseColour"] and surface_style["DiffuseColour"].is_a("IfcNormalisedRatioMeasure"): + diffuse_color_value = surface_style["DiffuseColour"].wrappedValue + diffuse_color = [v * diffuse_color_value for v in surface_style["SurfaceColor"][:3]] + [1] + surface_style["DiffuseColour"] = ("IfcNormalisedRatioMeasure", diffuse_color) + else: + surface_style["DiffuseColour"] = None + + if surface_style["SpecularColour"] and surface_style["SpecularColour"].is_a("IfcNormalisedRatioMeasure"): + surface_style["SpecularColour"] = surface_style["SpecularColour"].wrappedValue + else: + surface_style["SpecularColour"] = None + + if surface_style["SpecularHighlight"] and surface_style["SpecularHighlight"].is_a("IfcSpecularRoughness"): + surface_style["SpecularHighlight"] = surface_style["SpecularHighlight"].wrappedValue + else: + surface_style["SpecularHighlight"] = None + return surface_style + @classmethod def create_surface_style_rendering(cls, blender_material, surface_style): + surface_style = cls.surface_style_to_dict(surface_style) cls.create_surface_style_shading(blender_material, surface_style) - if surface_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]: + + reflectance_method = surface_style["ReflectanceMethod"] + if reflectance_method not in ("PHYSICAL", "NOTDEFINED", "FLAT"): + print(f'WARNING. Unsupported reflectance method "{reflectance_method}" on style {surface_style}') + return + + if reflectance_method in ["PHYSICAL", "NOTDEFINED"]: blender_material.use_nodes = True cls.restart_material_node_tree(blender_material) bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED") - if surface_style.DiffuseColour: - if surface_style.DiffuseColour.is_a("IfcColourRgb"): - bsdf.inputs["Base Color"].default_value = ( - surface_style.DiffuseColour.Red, - surface_style.DiffuseColour.Green, - surface_style.DiffuseColour.Blue, - 1, - ) - elif surface_style.DiffuseColour.is_a("IfcNormalisedRatioMeasure"): - bsdf.inputs["Base Color"].default_value = ( - surface_style.SurfaceColour.Red * surface_style.DiffuseColour.wrappedValue, - surface_style.SurfaceColour.Green * surface_style.DiffuseColour.wrappedValue, - surface_style.SurfaceColour.Blue * surface_style.DiffuseColour.wrappedValue, - 1, - ) - if surface_style.SpecularColour and surface_style.SpecularColour.is_a("IfcNormalisedRatioMeasure"): - bsdf.inputs["Metallic"].default_value = surface_style.SpecularColour.wrappedValue - if surface_style.SpecularHighlight and surface_style.SpecularHighlight.is_a("IfcSpecularRoughness"): - bsdf.inputs["Roughness"].default_value = surface_style.SpecularHighlight.wrappedValue - if hasattr(surface_style, "Transparency") and surface_style.Transparency: - bsdf.inputs["Alpha"].default_value = 1 - surface_style.Transparency + if surface_style["DiffuseColour"]: + color_type, color_value = surface_style["DiffuseColour"] + if color_type == "IfcColourRgb": + bsdf.inputs["Base Color"].default_value = color_value + elif color_type == "IfcNormalisedRatioMeasure": + bsdf.inputs["Base Color"].default_value = color_value + if surface_style["SpecularColour"]: + bsdf.inputs["Metallic"].default_value = surface_style["SpecularColour"] + if surface_style["SpecularHighlight"]: + bsdf.inputs["Roughness"].default_value = surface_style["SpecularHighlight"] + if transparency := surface_style.get("Transparency", None): + bsdf.inputs["Alpha"].default_value = 1 - transparency blender_material.blend_method = "BLEND" - elif surface_style.ReflectanceMethod == "FLAT": + elif reflectance_method == "FLAT": blender_material.use_nodes = True cls.restart_material_node_tree(blender_material) - output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None) + output = tool.Blender.get_material_node(blender_material, "OUTPUT_MATERIAL") bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED") mix = blender_material.node_tree.nodes.new(type="ShaderNodeMixShader") @@ -123,24 +150,21 @@ class Loader(blenderbim.core.tool.Loader): blender_material.node_tree.nodes.remove(bsdf) lightpath = blender_material.node_tree.nodes.new(type="ShaderNodeLightPath") - lightpath.location = mix.location - mathutils.Vector((200, -200)) + lightpath.location = mix.location - Vector((200, -200)) blender_material.node_tree.links.new(lightpath.outputs[0], mix.inputs[0]) bsdf = blender_material.node_tree.nodes.new(type="ShaderNodeBsdfTransparent") - bsdf.location = mix.location - mathutils.Vector((200, 0)) + bsdf.location = mix.location - Vector((200, 150)) blender_material.node_tree.links.new(bsdf.outputs[0], mix.inputs[1]) rgb = blender_material.node_tree.nodes.new(type="ShaderNodeRGB") - rgb.location = mix.location - mathutils.Vector((200, 200)) + rgb.location = mix.location - Vector((200, 250)) blender_material.node_tree.links.new(rgb.outputs[0], mix.inputs[2]) - if surface_style.DiffuseColour and surface_style.DiffuseColour.is_a("IfcColourRgb"): - rgb.outputs[0].default_value = ( - surface_style.DiffuseColour.Red, - surface_style.DiffuseColour.Green, - surface_style.DiffuseColour.Blue, - 1, - ) + if surface_style["DiffuseColour"]: + color_type, color_value = surface_style["DiffuseColour"] + if color_type == "IfcColourRgb": + rgb.outputs[0].default_value = color_value @classmethod def create_surface_style_with_textures(cls, blender_material, rendering_style, texture_style): @@ -153,73 +177,91 @@ class Loader(blenderbim.core.tool.Loader): if not os.path.abspath(texture.URLReference) and tool.Ifc.get_path(): image_url = os.path.join(os.path.dirname(tool.Ifc.get_path()), texture.URLReference) + 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}') + return + if rendering_style.ReflectanceMethod in ["PHYSICAL", "NOTDEFINED"]: bsdf = tool.Blender.get_material_node(blender_material, "BSDF_PRINCIPLED") if mode == "NORMAL": + # add normal map node normalmap = blender_material.node_tree.nodes.new(type="ShaderNodeNormalMap") - normalmap.location = bsdf.location - mathutils.Vector((200, 0)) + normalmap.location = bsdf.location - Vector((200, 0)) blender_material.node_tree.links.new(normalmap.outputs[0], bsdf.inputs["Normal"]) + # add normal map sampler node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage") - node.location = normalmap.location - mathutils.Vector((200, 0)) + node.location = normalmap.location - Vector((200, 0)) image = bpy.data.images.load(image_url) image.colorspace_settings.name = "Non-Color" node.image = image blender_material.node_tree.links.new(node.outputs[0], normalmap.inputs["Color"]) - elif mode == "EMISSIVE": - output = {n.type: n for n in blender_material.node_tree.nodes}.get("OUTPUT_MATERIAL", None) + elif mode == "EMISSIVE": + output = tool.Blender.get_material_node(blender_material, "OUTPUT_MATERIAL") + + # add "Add Shader" node add = blender_material.node_tree.nodes.new(type="ShaderNodeAddShader") - add.location = bsdf.location + mathutils.Vector((200, 0)) + add.location = bsdf.location + Vector((200, 0)) blender_material.node_tree.links.new(bsdf.outputs[0], add.inputs[1]) blender_material.node_tree.links.new(add.outputs[0], output.inputs[0]) + # add emssion shader node emission = blender_material.node_tree.nodes.new(type="ShaderNodeEmission") - emission.location = add.location - mathutils.Vector((200, 0)) + emission.location = add.location - Vector((200, 0)) blender_material.node_tree.links.new(emission.outputs[0], add.inputs[0]) + # add emission texture sampler node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage") - node.location = emission.location - mathutils.Vector((200, 0)) + node.location = emission.location - Vector((200, 0)) image = bpy.data.images.load(image_url) node.image = image blender_material.node_tree.links.new(node.outputs[0], emission.inputs[0]) + elif mode == "METALLICROUGHNESS": separate = blender_material.node_tree.nodes.new(type="ShaderNodeSeparateRGB") - separate.location = bsdf.location - mathutils.Vector((200, 0)) + separate.location = bsdf.location - Vector((200, 0)) blender_material.node_tree.links.new(separate.outputs[1], bsdf.inputs["Roughness"]) blender_material.node_tree.links.new(separate.outputs[2], bsdf.inputs["Metallic"]) node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage") - node.location = separate.location - mathutils.Vector((200, 0)) + node.location = separate.location - Vector((200, 0)) image = bpy.data.images.load(image_url) image.colorspace_settings.name = "Non-Color" node.image = image blender_material.node_tree.links.new(node.outputs[0], separate.inputs[0]) + elif mode == "OCCLUSION": # TODO work out how to implement glTF settings here # https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html pass + elif mode == "DIFFUSE": node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage") - node.location = bsdf.location - mathutils.Vector((400, 0)) + node.location = bsdf.location - Vector((400, 0)) image = bpy.data.images.load(image_url) node.image = image blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs["Base Color"]) blender_material.node_tree.links.new(node.outputs[1], bsdf.inputs["Alpha"]) blender_material.blend_method = "BLEND" + elif rendering_style.ReflectanceMethod == "FLAT": bsdf = tool.Blender.get_material_node(blender_material, "MIX_SHADER") - if mode == "EMISSIVE": - node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage") - node.location = bsdf.location - mathutils.Vector((200, 0)) - image = bpy.data.images.load(image_url) - node.image = image - blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2]) + if mode != "EMISSIVE": + continue + + node = blender_material.node_tree.nodes.new(type="ShaderNodeTexImage") + node.location = bsdf.location - Vector((200, 0)) + image = bpy.data.images.load(image_url) + # TODO: orphaned textures after shader recreated? + node.image = image + blender_material.node_tree.links.new(node.outputs[0], bsdf.inputs[2]) if node and getattr(texture, "IsMappedBy", None): coordinates = texture.IsMappedBy[0] coord = blender_material.node_tree.nodes.new(type="ShaderNodeTexCoord") - coord.location = node.location - mathutils.Vector((200, 0)) + 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": diff --git a/src/blenderbim/blenderbim/tool/style.py b/src/blenderbim/blenderbim/tool/style.py index 8dfd6bf2a1..f360be55fe 100644 --- a/src/blenderbim/blenderbim/tool/style.py +++ b/src/blenderbim/blenderbim/tool/style.py @@ -24,6 +24,16 @@ import blenderbim.tool as tool import blenderbim.bim.helper +STYLE_PROPS_MAP = { + "reflectance_method": "ReflectanceMethod", + "diffuse_color": "DiffuseColour", + "surface_color": "SurfaceColour", + "transparency": "Transparency", + "roughness": "SpecularHighlight", + "metallic": "SpecularColour", +} + + class Style(blenderbim.core.tool.Style): @classmethod def can_support_rendering_style(cls, obj): @@ -87,6 +97,8 @@ class Style(blenderbim.core.tool.Style): @classmethod def get_style_elements(cls, blender_material): + if not blender_material.BIMMaterialProperties.ifc_style_id: + return {} style = tool.Ifc.get().by_id(blender_material.BIMMaterialProperties.ifc_style_id) style_elements = {} for style in style.Styles: @@ -94,52 +106,184 @@ class Style(blenderbim.core.tool.Style): return style_elements @classmethod - def get_surface_rendering_attributes(cls, obj): + def get_surface_style_from_props(cls, blender_material): + """convert blender style props to ifc props""" + surface_style_data = dict() + props = blender_material.BIMStyleProperties + for prop_blender, prop_ifc in STYLE_PROPS_MAP.items(): + surface_style_data[prop_ifc] = getattr(props, prop_blender) + if surface_style_data["ReflectanceMethod"] == "PHYSICAL" and tool.Ifc.get_schema() != "IFC4X3": + surface_style_data["ReflectanceMethod"] = "NOTDEFINED" + surface_style_data["DiffuseColour"] = ("IfcColourRgb", surface_style_data["DiffuseColour"]) + return surface_style_data + + @classmethod + def set_surface_style_props(cls, blender_material): + """set blender style props based on current surface material""" + props = blender_material.BIMStyleProperties + # make sure won't be updating while we changing it + prev_update_graph_value = props.update_graph + props["update_graph"] = False + + style_elements = tool.Style.get_style_elements(blender_material) + surface_style = style_elements["IfcSurfaceStyleRendering"] + style_data = tool.Loader.surface_style_to_dict(surface_style) + if style_data["ReflectanceMethod"] == "NOTDEFINED": + style_data["ReflectanceMethod"] = "PHYSICAL" + style_data["DiffuseColour"] = style_data["DiffuseColour"][1] + + for prop_blender, prop_ifc in STYLE_PROPS_MAP.items(): + prop_value = style_data[prop_ifc] + # TODO: set to default? + if prop_value is None: + continue + setattr(props, prop_blender, prop_value) + + props["update_graph"] = prev_update_graph_value + + @classmethod + def get_surface_rendering_attributes(cls, obj, verbose=True): transparency = 1 - obj.diffuse_color[3] diffuse_color = obj.diffuse_color + report = (lambda *x: print(*x)) if verbose else (lambda *x: None) + + viewport_color = { + "Name": None, + "Red": obj.diffuse_color[0], + "Green": obj.diffuse_color[1], + "Blue": obj.diffuse_color[2], + } attributes = { - "SurfaceColour": { - "Name": None, - "Red": obj.diffuse_color[0], - "Green": obj.diffuse_color[1], - "Blue": obj.diffuse_color[2], - }, + "SurfaceColour": viewport_color, "Transparency": transparency, } + GREEN = "\033[32m" + BLUE = "\033[1;34m" + R = "\033[0m" # RESET symbol - bsdfs = {n.type: n for n in obj.node_tree.nodes} - if "BSDF_GLOSSY" in bsdfs: - attributes["ReflectanceMethod"] = "METAL" - bsdf = bsdfs["BSDF_GLOSSY"] + def get_input_node(node, input_name=None, of_type=None, input_index=None): + input_pin = node.inputs[input_name] if input_index is None else node.inputs[input_index] + if of_type: + return next((l.from_node for l in input_pin.links if l.from_node.type == of_type), None) + return next((l.from_node for l in input_pin.links), None) + + report("--------------------") + report("Verbose method of getting surface rendering attributes enabled.") + report("If some attribute is not mentioned below, then it won't be saved to IFC.") + report("--------------------") + report(f"{GREEN}Viewport color{R} saved as {GREEN}SurfaceColour{R}") + + # TODO: make sure bsdf is connected to the output? + bsdfs = {n.type: n for n in obj.node_tree.nodes if n.outputs and n.outputs[0].is_linked} + if "BSDF_PRINCIPLED" not in bsdfs: + report(f"{GREEN}Viewport color alpha{R} saved as {GREEN}Transparency{R}") + + # TODO: should escape referring to pins by their name to support different languages + material_output = tool.Blender.get_material_node(obj, "OUTPUT_MATERIAL", {"is_active_output": True}) + surface_output = get_input_node(material_output, "Surface") + + if surface_output and surface_output.type == "MIX_SHADER": + mix_shader = surface_output + if ( + get_input_node(mix_shader, "Fac", "LIGHT_PATH") + and get_input_node(mix_shader, input_index=1, of_type="BSDF_TRANSPARENT") + and (rgb := get_input_node(mix_shader, input_index=2, of_type="RGB")) + ): + report( + f"Because of {BLUE}MIX_SHADER + LIGHT_PATH + BSDF_TRANSPARENT + RGB{R} node setup reflectance method identified as {BLUE}FLAT{R}" + ) + attributes["ReflectanceMethod"] = "FLAT" + attributes["SpecularHighlight"] = None + report(f"RGB {GREEN}Color{R} saved as {GREEN}DiffuseColour{R}") + diffuse_color = rgb.outputs[0].default_value + + elif surface_output and surface_output.type == "BSDF_PRINCIPLED": + report(f"Because of {BLUE}BSDF_PRINCIPLED{R} node reflectance method identified as {BLUE}PHYSICAL{R}") + attributes["ReflectanceMethod"] = "NOTDEFINED" if tool.Ifc.get_schema() != "IFC4X3" else "PHYSICAL" + bsdf = surface_output + + report(f"BSDF {GREEN}Base Color{R} saved as {GREEN}DiffuseColour{R}") + diffuse_color = bsdf.inputs["Base Color"].default_value + + report(f"BSDF {GREEN}Metallic{R} saved as {GREEN}SpecularColour{R}") + attributes["SpecularColour"] = round(bsdf.inputs["Metallic"].default_value, 3) + + report(f"BSDF {GREEN}Roughness{R} saved as {GREEN}IfcSpecularRoughness{R}") attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)} + + report(f"BSDF {GREEN}Alpha{R} saved as {GREEN}Transparency{R}") + attributes["Transparency"] = 1 - bsdf.inputs["Alpha"].default_value + + elif "BSDF_GLOSSY" in bsdfs: + attributes["ReflectanceMethod"] = "METAL" + report(f"Because of {BLUE}BSDF_GLOSSY{R} node reflectance method identified as {BLUE}METAL{R}") + bsdf = bsdfs["BSDF_GLOSSY"] + + report(f"BSDF {GREEN}Roughness{R} saved as {GREEN}IfcSpecularRoughness{R}") + attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)} + + report(f"BSDF {GREEN}Color{R} saved as {GREEN}DiffuseColour{R}") diffuse_color = bsdf.inputs["Color"].default_value + elif "BSDF_DIFFUSE" in bsdfs: + report(f"Because of {BLUE}BSDF_DIFFUSE{R} node reflectance method identified as {BLUE}MATT{R}") attributes["ReflectanceMethod"] = "MATT" bsdf = bsdfs["BSDF_DIFFUSE"] + + report(f"BSDF {GREEN}Roughness{R} saved as {GREEN}IfcSpecularRoughness{R}") attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)} + + report(f"BSDF {GREEN}Color{R} saved as {GREEN}DiffuseColour{R}") diffuse_color = bsdf.inputs["Color"].default_value + elif "BSDF_GLASS" in bsdfs: + report(f"Because of {BLUE}BSDF_GLASS{R} node reflectance method identified as {BLUE}GLASS{R}") attributes["ReflectanceMethod"] = "GLASS" bsdf = bsdfs["BSDF_GLASS"] + + report(f"BSDF {GREEN}Roughness{R} saved as {GREEN}IfcSpecularRoughness{R}") attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)} + + report(f"BSDF {GREEN}Color{R} saved as {GREEN}DiffuseColour{R}") diffuse_color = bsdf.inputs["Color"].default_value + + # TODO: remove? elif "EMISSION" in bsdfs: + report(f"Because of {BLUE}EMISSION{R} node reflectance method identified as {BLUE}FLAT{R}") attributes["ReflectanceMethod"] = "FLAT" bsdf = bsdfs["EMISSION"] + attributes["SpecularHighlight"] = None + report(f"BSDF {GREEN}Color{R} saved as {GREEN}DiffuseColour{R}") + diffuse_color = bsdf.inputs["Color"].default_value + + # TODO: remove? elif "BSDF_PRINCIPLED" in bsdfs: - attributes["ReflectanceMethod"] = "NOTDEFINED" + report(f"Because of {BLUE}BSDF_PRINCIPLED{R} node reflectance method identified as {BLUE}PHYSICAL{R}") + attributes["ReflectanceMethod"] = "NOTDEFINED" if tool.Ifc.get_schema() != "IFC4X3" else "PHYSICAL" bsdf = bsdfs["BSDF_PRINCIPLED"] - attributes["SpecularColour"] = round(bsdf.inputs["Metallic"].default_value, 3) - attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)} + + report(f"BSDF {GREEN}Base Color{R} saved as {GREEN}DiffuseColour{R}") diffuse_color = bsdf.inputs["Base Color"].default_value + + report(f"BSDF {GREEN}Metallic{R} saved as {GREEN}SpecularColour{R}") + attributes["SpecularColour"] = round(bsdf.inputs["Metallic"].default_value, 3) + + report(f"BSDF {GREEN}Roughness{R} saved as {GREEN}IfcSpecularRoughness{R}") + attributes["SpecularHighlight"] = {"IfcSpecularRoughness": round(bsdf.inputs["Roughness"].default_value, 3)} + + report(f"BSDF {GREEN}Alpha{R} saved as {GREEN}Transparency{R}") attributes["Transparency"] = 1 - bsdf.inputs["Alpha"].default_value + else: + report(f"No supported bsdfs found - reflectance method identified as {BLUE}NOTDEFINED{R}") attributes["ReflectanceMethod"] = "NOTDEFINED" + attributes["SpecularHighlight"] = None - attributes["DiffuseColour"] = attributes["SurfaceColour"] + report(f"{GREEN}Viewport color{R} saved as {GREEN}DiffuseColour{R}") + attributes["DiffuseColour"] = viewport_color return attributes attributes["DiffuseColour"] = { @@ -148,16 +292,12 @@ class Style(blenderbim.core.tool.Style): "Green": diffuse_color[1], "Blue": diffuse_color[2], } - return attributes @classmethod def get_surface_rendering_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("IfcSurfaceStyleRendering")] - if items: - return items[0] + style_elements = cls.get_style_elements(obj) + return style_elements.get("IfcSurfaceStyleRendering", None) @classmethod def get_surface_shading_attributes(cls, obj):