def add_normal(nodes: bpy.types.Nodes, links: bpy.types.NodeLinks, normal_image_path: str, principled_bsdf: bpy.types.Node, invert_y_channel: bool): """ Adds normal to the principled bsdf node. :param nodes: Nodes from the current material :param links: Links from the current material :param normal_image_path: Path to the metal image :param principled_bsdf: Principled BSDF node of the current material :param invert_y_channel: If this is True the Y Color Channel is inverted. :return: bpy.types.Node: The newly constructed texture node """ normal_y_value = MaterialLoaderUtility.y_texture_node * -3 if os.path.exists(normal_image_path): normal_texture = MaterialLoaderUtility.create_image_node( nodes, normal_image_path, True, MaterialLoaderUtility.x_texture_node, normal_y_value) if invert_y_channel: separate_rgba = nodes.new('ShaderNodeSeparateRGB') separate_rgba.location.x = 4.0 / 5.0 * MaterialLoaderUtility.x_texture_node separate_rgba.location.y = normal_y_value links.new(normal_texture.outputs["Color"], separate_rgba.inputs["Image"]) invert_node = nodes.new("ShaderNodeInvert") invert_node.inputs["Fac"].default_value = 1.0 invert_node.location.x = 3.0 / 5.0 * MaterialLoaderUtility.x_texture_node invert_node.location.y = normal_y_value links.new(separate_rgba.outputs["G"], invert_node.inputs["Color"]) combine_rgba = nodes.new('ShaderNodeCombineRGB') combine_rgba.location.x = 2.0 / 5.0 * MaterialLoaderUtility.x_texture_node combine_rgba.location.y = normal_y_value links.new(separate_rgba.outputs["R"], combine_rgba.inputs["R"]) links.new(invert_node.outputs["Color"], combine_rgba.inputs["G"]) links.new(separate_rgba.outputs["B"], combine_rgba.inputs["B"]) current_output = combine_rgba.outputs["Image"] else: current_output = normal_texture.outputs["Color"] normal_map = nodes.new("ShaderNodeNormalMap") normal_map.inputs["Strength"].default_value = 1.0 normal_map.location.x = 1.0 / 5.0 * MaterialLoaderUtility.x_texture_node normal_map.location.y = normal_y_value links.new(current_output, normal_map.inputs["Color"]) links.new(normal_map.outputs["Normal"], principled_bsdf.inputs["Normal"]) return normal_texture return None
def add_displacement(nodes: bpy.types.Nodes, links: bpy.types.NodeLinks, displacement_image_path: str, output_node: bpy.types.Node): """ Adds bump to the principled bsdf node. :param nodes: Nodes from the current material :param links: Links from the current material :param displacement_image_path: Path to the metal image :param output_node: Output node of the current material :return: bpy.types.Node: The newly constructed texture node """ if os.path.exists(displacement_image_path): displacement_texture = create_image_node(nodes, displacement_image_path, True, x_texture_node, y_texture_node * -4) displacement_node = nodes.new("ShaderNodeDisplacement") displacement_node.inputs["Midlevel"].default_value = 0.5 displacement_node.inputs["Scale"].default_value = 0.15 displacement_node.location.x = x_texture_node * 0.5 displacement_node.location.y = y_texture_node * -4 links.new(displacement_texture.outputs["Color"], displacement_node.inputs["Height"]) links.new(displacement_node.outputs["Displacement"], output_node.inputs["Displacement"]) return displacement_texture return None
def add_ambient_occlusion(nodes: bpy.types.Nodes, links: bpy.types.NodeLinks, ambient_occlusion_image_path, principled_bsdf: bpy.types.Node, base_color: bpy.types.Node): """ Adds ambient occlusion to the principled bsdf node. :param nodes: Nodes from the current material :param links: Links from the current material :param ambient_occlusion_image_path: Path to the ambient occlusion image :param principled_bsdf: Principled BSDF node of the current material :param base_color: Base color node of the current material :return: bpy.types.Node: The newly constructed texture node """ if os.path.exists(ambient_occlusion_image_path): ao_color = create_image_node(nodes, ambient_occlusion_image_path, True, x_texture_node, y_texture_node * 2) math_node = nodes.new(type='ShaderNodeMixRGB') math_node.blend_type = "MULTIPLY" math_node.location.x = x_texture_node * 0.5 math_node.location.y = y_texture_node * 1.5 math_node.inputs["Fac"].default_value = 0.333 links.new(base_color.outputs["Color"], math_node.inputs[1]) links.new(ao_color.outputs["Color"], math_node.inputs[2]) links.new(math_node.outputs["Color"], principled_bsdf.inputs["Base Color"]) return ao_color return None
def create_image_node(nodes: bpy.types.Nodes, image: Union[str, bpy.types.Image], non_color_mode=False, x_location=0, y_location=0): """ Creates a texture image node inside of a material. :param nodes: Nodes from the current material :param image: Either the path to the image which should be loaded or the bpy.types.Image :param non_color_mode: If this True, the color mode of the image will be "Non-Color" :param x_location: X Location in the node tree :param y_location: Y Location in the node tree :return: bpy.type.Node: Return the newly constructed image node """ image_node = nodes.new('ShaderNodeTexImage') if isinstance(image, bpy.types.Image): image_node.image = image else: image_node.image = bpy.data.images.load(image, check_existing=True) if non_color_mode: image_node.image.colorspace_settings.name = 'Non-Color' image_node.location.x = x_location image_node.location.y = y_location return image_node
def add_bump(nodes: bpy.types.Nodes, links: bpy.types.NodeLinks, bump_image_path: str, principled_bsdf: bpy.types.Node): """ Adds bump to the principled bsdf node. :param nodes: Nodes from the current material :param links: Links from the current material :param bump_image_path: Path to the metal image :param principled_bsdf: Principled BSDF node of the current material :return: bpy.types.Node: The newly constructed texture node """ bump_y_value = MaterialLoaderUtility.y_texture_node * -3 if os.path.exists(bump_image_path): bump_texture = MaterialLoaderUtility.create_image_node( nodes, bump_image_path, True, MaterialLoaderUtility.x_texture_node, bump_y_value) bump_map = nodes.new("ShaderNodeBumpMap") bump_map.inputs["Strength"].default_value = 1.0 bump_map.location.x = 1.0 / 5.0 * MaterialLoaderUtility.x_texture_node bump_map.location.y = bump_y_value links.new(bump_texture.outputs["Color"], bump_map.inputs["Heights"]) links.new(bump_map.outputs["Normal"], principled_bsdf.inputs["Normal"]) return bump_texture return None
def copy_nodes(nodes: bpy.types.Nodes, goal_nodes: bpy.types.Nodes): """ Copies all nodes from the given list into the group with their attributes :param: node: the nodes which should be copied :param: goal_nodes: the nodes where they should be copied too """ if len(goal_nodes) > 0: raise Exception( f"This function only works if goal_nodes was empty before, has {len(goal_nodes)} elements." ) # the attributes that should be copied for every link input_attributes = ["default_value", "name"] output_attributes = ["default_value", "name"] for node in nodes: # create a new node in the goal_nodes and find and copy its attributes new_node = goal_nodes.new(node.bl_idname) node_attributes = get_node_attributes(node) copy_attributes(node_attributes, node, new_node) # copy the attributes for all inputs for inp, new_inp in zip(node.inputs, new_node.inputs): copy_attributes(input_attributes, inp, new_inp) # copy the attributes for all outputs for out, new_out in zip(node.outputs, new_node.outputs): copy_attributes(output_attributes, out, new_out)
def create_node( nodes: bpy.types.Nodes, node_attr_name: str, _type: str, pos: Iterable[float] = None, inputs: dict = None, # Index (integer or string), value outputs: dict = None, # Index (integer or string), value attributes: dict = None) -> None: """ Gets the node with the specified name from the nodes Or Creates it if it doesnt exist Then updates the inputs, outputs and attributes if necessary Then adds it to the MaterialManager for easy access """ # node = nodes.get(node_attr_name) # if not node: node = nodes.new(type=_type) node.name = node_attr_name if pos: node.location = pos if inputs: for index, value in inputs.items(): node.inputs[index].default_value = value if outputs: for index, value in outputs.items(): node.outputs[index].default_value = value if attributes: for name, value in attributes.items(): setattr(node, name, value)
def connect_uv_maps(nodes: bpy.types.Nodes, links: bpy.types.NodeLinks, collection_of_texture_nodes: list): """ Connect all given texture nodes to a newly constructed UV node. :param nodes: Nodes from the current material :param links: Links from the current material :param collection_of_texture_nodes: List of :class: `bpy.type.Node` of type :class: `ShaderNodeTexImage` """ if len(collection_of_texture_nodes) > 0: texture_coords = nodes.new("ShaderNodeTexCoord") texture_coords.location.x = MaterialLoaderUtility.x_texture_node * 1.4 mapping_node = nodes.new("ShaderNodeMapping") mapping_node.location.x = MaterialLoaderUtility.x_texture_node * 1.2 links.new(texture_coords.outputs["UV"], mapping_node.inputs["Vector"]) for texture_node in collection_of_texture_nodes: if texture_node is not None: links.new(mapping_node.outputs["Vector"], texture_node.inputs["Vector"])
def get_or_create_node( nodes: bpy.types.Nodes, node_attr_name: str, _type: str, pos: Iterable[float] = None, inputs: dict = None, # Index (integer or string), value outputs: dict = None, # Index (integer or string), value attributes: dict = None) -> None: node = nodes.get(node_attr_name) if not node: node = nodes.new(type=_type) node.name = node_attr_name if pos: node.location = pos if inputs: for index, value in inputs.items(): node.inputs[index].default_value = value if outputs: for index, value in outputs.items(): node.outputs[index].default_value = value if attributes: for name, value in attributes.items(): setattr(node, name, value) setattr(MaterialManager, node_attr_name, node)
def clean_nodes(nodes: bpy.types.Nodes) -> None: for node in nodes: nodes.remove(node)