1
$\begingroup$

I am attempting to create a displacement node with an attached image, and connect that to the Main Material Output Node.

I feel I understand how to create and connect these pieces (mostly) fine now. But I am unable to locate/reference the original Material Output, OR how to make a new Material Output the assigned one.

Please forgive my code, it is a hacked attempt between many forums posts and my own understanding after 1 week into python and blender.

Thank you in advance (code and pictures below)


Images:

What I want whatIWant What I have whatIHave


Code

#Import python import bpy #Import additional refs from bpy import context, data, ops # Clear all nodes in a mat def clear_material( material ): if material.node_tree: material.node_tree.links.clear() material.node_tree.nodes.clear() # Create a node corresponding to a defined group def instanciate_group( nodes, group_name ): group = nodes.new( type = 'ShaderNodeGroup' ) group.node_tree = bpy.data.node_groups[group_name] #ref materials materials = bpy.data.materials #name material mat_name = 'DELETE_THIS_CODE' # get ref to material material = materials.get( mat_name ) #if not our mat if not material: material = materials.new( mat_name ) # We clear it as we'll define it completely clear_material( material ) material.use_nodes = True nodes = material.node_tree.nodes links = material.node_tree.links output = nodes.new( type = 'ShaderNodeOutputMaterial' ) diffuse = nodes.new( type = 'ShaderNodeBsdfDiffuse' ) #input = nodes.new( type = 'ShaderNodeTexImage') #With names link = links.new( diffuse.outputs['BSDF'], output.inputs['Surface']) #Or with indices #link = links.new( diffuse.outputs[0], output.inputs[0] ) #______________________^^ multiple things above here might be deletable ^^_____________ #________________________________________________Adds img texture #Create material named X mat = bpy.data.materials.new(name="Mat_Tile") #Set this material to use nodes mat.use_nodes = True #Add new Material output #moNode = mat.node_tree.nodes.new('ShaderNodeOutputMaterial') bsdf = mat.node_tree.nodes["Principled BSDF"] texImage = mat.node_tree.nodes.new('ShaderNodeTexImage') texImage.image = bpy.data.images.load("C:\\Users\\nmaestre\\Downloads\\Textures\\Downloaded\\Tile_00_Diffuse.png") mat.node_tree.links.new(bsdf.inputs['Base Color'], texImage.outputs['Color']) #reference selected object ob = context.view_layer.objects.active # Assign it to object if ob.data.materials: ob.data.materials[0] = mat else: ob.data.materials.append(mat) #Add displace dNode = mat.node_tree.nodes.new('ShaderNodeDisplacement') #Make a normal map node #nNode = mat.node_tree.nodes.new('ShaderNodeNormalMap') #Displace Img texImageNormal = mat.node_tree.nodes.new('ShaderNodeTexImage') texImageNormal.image = bpy.data.images.load("C:\\Users\\nmaestre\\Downloads\\Textures\\Downloaded\\Tile_00_Normal.png") #Attach imag to diplace node mat.node_tree.links.new(texImageNormal.outputs['Color'], dNode.inputs['Normal']) #connect the Displacement node to the Material Output node mat.node_tree.links.new(dNode.outputs['Displacement'], bsdf.inputs['Normal']) #__________________________Add unwrap #uwrap node tex_coord = nodes.new(type = 'ShaderNodeTexCoord') #link to image texture links.new(tex_coord.outputs["UV"], texImage.inputs["Vector"]) 
$\endgroup$

2 Answers 2

3
$\begingroup$

The Material Output can be retrieved by name or by type. If you know the name of the node and the variable nodes contains a reference to the nodes of the material's node tree, then you can access the Material Output node through a look up where the name is used as key:

material_output = nodes.get("Material Output") 

A material may contain more than one Material Output node or it may have been renamed. In this case you could search for a node that is of type OUTPUT_MATERIAL.

material_output = None for node in nodes: if node.type == "OUTPUT_MATERIAL": material_output = node break 

In both cases it may happen that no Material Output node exists, for instance because you're working on an existing material where it was deleted. Therefore you need to check if material_output is None and if it is create a new node.

if material_output is None: material_output = nodes.new("ShaderNodeOutputMaterial") 

When you're creating a new material from scratch you do know that both the Principled BSDF and Material Output nodes exist and you can access them by name.


In order to connect two nodes you need access to the material's node tree links. Assuming the variable links references these, you can add new links through the new() function. The first argument is the input socket of a node and the second argument is the output socket of a node. Assuming that principled_bsdf would reference a Principled BSDF node and base_color an Image Texture node, you could connect them in the following way:

links.new(principled_bsdf.inputs["Base Color"], base_color.outputs["Color"]) 

The following code shows how to create your desired node setup by creating a new material which contains a Principled BSDF and Material Output node by default.

import bpy # Define names and paths material_name = "Example_Material" base_color_path = "" # Add your path here normal_map_path = "" # Add your path here # Create a material material = bpy.data.materials.new(name=material_name) material.use_nodes = True nodes = material.node_tree.nodes links = material.node_tree.links # Since you want a Principled BSDF and the Material Output node # in your material, we can re-use the nodes that are automatically # created. principled_bsdf = nodes.get("Principled BSDF") material_output = nodes.get("Material Output") # Create Image Texture node and load the base color texture base_color = nodes.new('ShaderNodeTexImage') base_color.image = bpy.data.images.load(base_color_path) # Create Image Texture node and load the normal map normal_tex = nodes.new('ShaderNodeTexImage') normal_tex.image = bpy.data.images.load(normal_map_path) # Set the color space to non-color, since normal maps contain # the direction of the surface normals and not color data normal_tex.image.colorspace_settings.name = "Non-Color" # Create the Displacement node displacement = nodes.new('ShaderNodeDisplacement') # Connect the base color texture to the Principled BSDF links.new(principled_bsdf.inputs["Base Color"], base_color.outputs["Color"]) # Connect the normal map to the Displacement node links.new(displacement.inputs["Height"], normal_tex.outputs["Color"]) # Connect the Displacement node to the Material Output node links.new(material_output.inputs["Displacement"], displacement.outputs["Displacement"]) 

The following example is for editing an existing material:

import bpy # Define names and paths material_name = "Example_Material" base_color_path = "" # Add your path here normal_map_path = "" # Add your path here # In case you're modifying an existing material you # will have to check whether or not it contains a # Material Output node. If it doesn't it has to be # created. material = bpy.data.materials[material_name] material.use_nodes = True nodes = material.node_tree.nodes links = material.node_tree.links # In case you don't know the name of the nodes you can also search # by type. This can be useful when you edit an existing material # where the nodes could've been renamed. # Find the Material Output node, if it exists material_output = None for node in nodes: if node.type == "OUTPUT_MATERIAL": material_output = node break # Perhaps the nodes hasn't been found then you'll have to create it if material_output is None: material_output = nodes.new("ShaderNodeOutputMaterial") # Create a Principled BSDF nodes principled_bsdf = nodes.new("ShaderNodeBsdfPrincipled") # Create Image Texture node and load the base color texture base_color = nodes.new('ShaderNodeTexImage') base_color.image = bpy.data.images.load(base_color_path) # Create Image Texture node and load the normal map normal_tex = nodes.new('ShaderNodeTexImage') normal_tex.image = bpy.data.images.load(normal_map_path) # Set the color space to non-color, since normal maps contain # the direction of the surface normals and not color data normal_tex.image.colorspace_settings.name = "Non-Color" # Create the Displacement node displacement = nodes.new('ShaderNodeDisplacement') # Connect the base color texture to the Principled BSDF links.new(principled_bsdf.inputs["Base Color"], base_color.outputs["Color"]) # Connect the normal map to the Displacement node links.new(displacement.inputs["Height"], normal_tex.outputs["Color"]) # Connect the Principled BSDF node to the Material Output node links.new(material_output.inputs["Surface"], principled_bsdf.outputs["BSDF"]) # Connect the Displacement node to the Material Output node links.new(material_output.inputs["Displacement"], displacement.outputs["Displacement"]) 

If you want to assign the material to the active object in Blender 2.8x then the the following line can be added to either of the scripts above. We do not need to retrieve the material again, since we already have a reference to it in the variable material.

bpy.context.active_object.active_material = material 
$\endgroup$
6
  • $\begingroup$ You're response and details are very much appreciated. Thank you for your time, Robert. I had been researching a bit and struggled to find or understand the searching of nodes. $\endgroup$ Commented Nov 13, 2019 at 12:50
  • $\begingroup$ Attempting the first method does not cause errors, but also does not work for me. _____________________________________________________________________________________ material_output = nodes.get("Material Output") _____________________________________________________________________________________ #connect the Displacement node to the Material Output node mat.node_tree.links.new(dNode.outputs['Displacement'], material_output.inputs['Displacement']) $\endgroup$ Commented Nov 13, 2019 at 12:54
  • $\begingroup$ *** The second method does not work for this as well. The same issue occurs where the code runs fine, but no connection is made using code that does connect for other methods on the same nodes. _____________________________________________________________________________________ I am 100% positive there is a Material Output node because when I made code to create one, it was never applied to anything because one already existed. _____________________________________________________________________________________ I'm struggling to debug why MO would not be accessable with these $\endgroup$ Commented Nov 13, 2019 at 12:59
  • $\begingroup$ The code you wrote for my specific setup does work. Unfortunately, it does not combine easily with the "attach to object" code I had. If you see this, I would be interested in the process to appropriately assign the material as well. Thank you. $\endgroup$ Commented Nov 13, 2019 at 13:17
  • 1
    $\begingroup$ @WillSmithsRobot Sure I can add an example of how to assign a material to an object. $\endgroup$ Commented Nov 13, 2019 at 13:50
0
$\begingroup$

With the Help of @Robert Gutzkow & a post from [here], I have a working-as-intended/needed script. Thank you for your help, and I hope this helps someone else too!

#Import python import bpy #Import additional refs from bpy import context, data, ops # Define names and paths material_name = "Material_KitchenTile" base_color_path = "C:\\Users\\nmaestre\\Downloads\\Textures\\Downloaded\\Tile_00_Diffuse.png" # Add your path here height_map_path = "C:\\Users\\nmaestre\\Downloads\\Textures\\Downloaded\\Tile_00_Height.png" # Add your path here # Create a material material = bpy.data.materials.new(name=material_name) material.use_nodes = True nodes = material.node_tree.nodes links = material.node_tree.links # Since you want a Principled BSDF and the Material Output node # in your material, we can re-use the nodes that are automatically # created. principled_bsdf = nodes.get("Principled BSDF") material_output = nodes.get("Material Output") # Create Image Texture node and load the base color texture base_color = nodes.new('ShaderNodeTexImage') base_color.image = bpy.data.images.load(base_color_path) # Create Image Texture node and load the normal map displ_tex = nodes.new('ShaderNodeTexImage') displ_tex.image = bpy.data.images.load(height_map_path) # Set the color space to non-color, since normal maps contain # the direction of the surface normals and not color data displ_tex.image.colorspace_settings.name = "Non-Color" # Create the Displacement node displacement = nodes.new('ShaderNodeDisplacement') # Connect the base color texture to the Principled BSDF links.new(principled_bsdf.inputs["Base Color"], base_color.outputs["Color"]) # Connect the normal map to the Displacement node links.new(displacement.inputs["Height"], displ_tex.outputs["Color"]) # Connect the Displacement node to the Material Output node links.new(material_output.inputs["Displacement"], displacement.outputs["Displacement"]) #__________Assign the material #Ref selected obj ob = bpy.context.active_object # Get material mat = bpy.data.materials.get(material_name) # Assign it to object if ob.data.materials: # assign to 1st material slot ob.data.materials[0] = mat else: # no slots ob.data.materials.append(mat) 
$\endgroup$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.