Source code for blendersynth.blender.material

import bpy
from .bsyn_object import BsynObject
from .nodes import tidy_tree
from ..utils import io
from ..utils import version


def _new_shader_node(node_tree, node_type):
    shader_node = node_tree.nodes.new(type=node_type)
    if node_type == "ShaderNodeBsdfPrincipled":
        spec_key = "Specular IOR Level" if version.is_version_plus(4) else "Specular"
        shader_node.inputs[spec_key].default_value = 0

    return shader_node


[docs] class Material(BsynObject): """BlenderSynth Material class. Will always be a node material.""" def __init__( self, name="NewMaterial", shader_type="ShaderNodeBsdfPrincipled", mat=None ): if mat is None: mat = bpy.data.materials.new(name) mat.use_nodes = True mat.node_tree.nodes.clear() self.shader = _new_shader_node(mat.node_tree, shader_type) self.output = mat.node_tree.nodes.new(type="ShaderNodeOutputMaterial") mat.node_tree.links.new( self.shader.outputs[0], self.output.inputs["Surface"] ) else: self.shader = None self.output = mat.node_tree.nodes["Material Output"] mat.use_nodes = True self._object = mat self.node_tree = mat.node_tree self.nodes = self.node_tree.nodes self.links = self.node_tree.links self._image_nodes = {} # dict of shader input socket : image node # set-up scaling self._add_scaling_node() self._scale = 1 tidy_tree(self.node_tree)
[docs] @classmethod def from_image( cls, image_loc, name="ImageMaterials", shader_type="ShaderNodeBsdfPrincipled", use_global_scale=True, ) -> "Material": """ Create a new material from an image texture. :param image_loc: Location of the image :param name: Name of the material :param shader_type: Shader type :param use_global_scale: Flag to scale the image texture node by the global scale :return: New Material instance """ mat = cls(name, shader_type=shader_type) # new instance mat.add_source( image_loc, input_name="Base Color", use_global_scale=use_global_scale ) return mat
[docs] def add_source( self, image_loc: str, input_name: str = "Base Color", use_global_scale: bool = True, allow_duplicate: bool = False, ): """ Add an image texture node to the material node tree, and connect it to the specified input of the Shader. :param image_loc: Image location :param input_name: Input socket name of the Shader to connect the image texture node to :param use_global_scale: Flag to scale the image texture node by the global scale :param allow_duplicate: Flag to allow duplicate image texture nodes (by default, a node to the same input will overwrite any previous) :return: """ # Get the nodes in the material node tree nodes = self.obj.node_tree.nodes links = self.obj.node_tree.links # Find the Principled BSDF principled = next( (node for node in nodes if node.type == "BSDF_PRINCIPLED"), None ) if not principled: raise ValueError( "add_source requires a Principled BSDF node in the material node tree" ) if input_name in self._image_nodes and not allow_duplicate: tex_image_node = self._image_nodes[input_name] else: tex_image_node = nodes.new("ShaderNodeTexImage") tex_image_node.image = io.load_image(image_loc) self._image_nodes[input_name] = tex_image_node if input_name == "Displacement": # Connect to the 'Displacement' socket of the output node links.new( tex_image_node.outputs["Color"], self.output.inputs["Displacement"] ) else: # Connect the image texture node to the specified input of the Principled BSDF links.new(tex_image_node.outputs["Color"], principled.inputs[input_name]) if use_global_scale: links.new(self._texture_scaling_socket, tex_image_node.inputs["Vector"]) if "color" not in input_name.lower(): tex_image_node.image.colorspace_settings.name = "Non-Color" tidy_tree(self.node_tree)
@classmethod def from_blender_material(cls, blender_mat: bpy.types.Material): if not isinstance(blender_mat, bpy.types.Material): raise ValueError("Invalid Blender material") # Create a new Material instance mat = cls(blender_mat.name, mat=blender_mat) # Find the shader and output nodes in the copied node tree mat.shader = next( (node for node in mat.nodes if node.type == "BSDF_PRINCIPLED"), None ) mat.output = next( (node for node in mat.nodes if node.type == "OUTPUT_MATERIAL"), None ) return mat def _add_scaling_node(self): """Add scaling node to the material node tree""" self.scale_node = self.nodes.new("ShaderNodeValue") tex_coord_node = self.nodes.new("ShaderNodeTexCoord") mapping_node = self.nodes.new("ShaderNodeMapping") self.links.new(self.scale_node.outputs[0], mapping_node.inputs["Scale"]) self.links.new(tex_coord_node.outputs["UV"], mapping_node.inputs["Vector"]) self._texture_scaling_socket = mapping_node.outputs["Vector"] self.scale = 1 # set default scale
[docs] def set_bdsf_property(self, key: str, value: float): """Set the property of the BSDF node""" if self.shader.type != "BSDF_PRINCIPLED": raise ValueError("Shader is not a Principled BSDF") if key not in self.shader.inputs: raise ValueError(f"Invalid BDSF shader property `{key}`") self.shader.inputs[key].default_value = value
@property def scale(self): return self._scale @scale.setter def scale(self, value): self._scale = value self.scale_node.outputs[0].default_value = value @property def name(self): return self.obj.name