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):