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.

Not the answer you're looking for? Browse other questions tagged .