QUESTION: Linking Cycles Nodes With Python?

Hello,

I’m still new to python, although I have made many running scripts for personal use.

I’m trying to make an addon that will add an object with a material and texture in Cycles. I’ve kept my eye on the “History”
when adding nodes in Cycles, but they appear to have no useful information about what the code is to attach/link the nodes together.

If you would please let me know what the appropriate line of code is, it would be very much appreciated.

Also, unrelated to nodes, if someone knows the code(s) to adding a random value to set a procedural texture size or type (Cell, Voronoi, New Perlin), that would be awesome as well!

I look forward to hearing from you, and thank you for responding!

-Blake

This script creates a cube with a random type texture defined in a dictionary and assigns a random scale within an arbitrary range.
The important thing to know for creating nodes its their type/bl_idname (e.g. “ShaderNodeDiffuse”). Before 2.67 the nomenclature was changed, so this works only for 2.67 and above.
A list of the changes is here: http://wiki.blender.org/index.php/Extensions:2.6/Py/API_Changes

To find out what bl_idname certain node is, in the console:

bpy.data.materials[“your_material_name_or_index”].node_tree.nodes[“your_node_name_or_index”].bl_idname


>>> bpy.data.materials[0].node_tree.nodes[0].bl_idname
'ShaderNodeOutputMaterial'



import random
import bpy


#Create a new material for cycles Engine.
bpy.context.scene.render.engine = 'CYCLES'
material = bpy.data.materials.new("material")
material.use_nodes= True
#Removing the default created nodes (diffuse-->output)
material.node_tree.nodes.clear()
    
#mapping nodes types for random access.
texture_type = { 0 : 'ShaderNodeTexVoronoi', 
                 1 : 'ShaderNodeTexMagic', 
                 2 : 'ShaderNodeTexMusgrave'
                    }


#create a random texture
random_texture = material.node_tree.nodes.new(type = texture_type.get(random.randrange(3) ))






#assign a random float between 1 and 10 for the scale, if the random texture has that attribute
if 'Scale' in random_texture.inputs:
    random_texture.inputs['Scale'].default_value = random.uniform(1,10)




#create other nodes
diffuse = material.node_tree.nodes.new(type = 'ShaderNodeBsdfDiffuse')
output = material.node_tree.nodes.new(type = 'ShaderNodeOutputMaterial')


#Link them togheter
material.node_tree.links.new(random_texture.outputs['Color'], diffuse.inputs['Normal'])
material.node_tree.links.new(diffuse.outputs['BSDF'], output.inputs['Surface'])


#Do some ordering, all nodes by default are at location 0,0


random_texture.location = (-400,0)
output.location = (400,0)




if bpy.context.mode != 'OBJECT':
        bpy.ops.object.mode_set(mode='OBJECT')


bpy.ops.mesh.primitive_cube_add()
bpy.context.object.data.materials.append(material)

Thank you for the reply!

The random scale portion was poorly explained by me. I needed to find out how to set a random scale for a cloud texture for a displacement modifier, but couldn’t figure out how to set a texture in the Displacement.

Regardless, I decided to just use a random vertex select and scale with random.uniform with random proportional editing. So that solved that, sorry.

As for the Shader Nodes, I’ll try your idea and see how that works out. It seems to be the best answer I’ve gotten today! Thank you again!

Also, do you know how to import an image from a file? Say, if I made a file containing the .py file and an image texture, how would I go about setting the file address to call the image from the file?

One more thing, when creating links, how do you define which “Shader” input it uses in an Add or Mix Shader?

bpy.data.images.load(image_path). As the API reference says, it returns the image type, so if you do
im = bpy.data.images.load(image_path), then you can use im to add it to a texture node.http://www.blender.org/documentation/blender_python_api_2_69_release/bpy.types.BlendDataImages.html#bpy.types.BlendDataImages.load

With mix and add nodes you have to use indices since both inputs are called “Shader” (either 0 or 1 for add, 1 or 2 for mix)

Forgive my ignorance, but how does one add indices? And if I’m using both “Shader” inputs for Add and Mix, will the indices have to be 0 or 1 and 2 or 3, or does it make a difference?

Also, is the image path copy pasted from something like Windows File browser, or is there a specific way you have to enter it?

And one more, will adding the image to the node still have to have something like tex_image.input(enter rest here), or is there something else?

Sorry for all the questions, I’m still learning. I haven’t quite gotten into the flow of it yet.

So far, all of these setting work, except for the comment versions of the links, just need to figure out what to add to separate the Shader links.

#Sets up basics/ Starts Cycles Engine, Creates new Material, Enables nodes
bpy.context.scene.render.engine = 'CYCLES'
rockMat = bpy.data.materials.new("GlowingRock_1")
rockMat.use_nodes= True


#Clears default nodes
rockMat.node_tree.nodes.clear()


#Adds new nodes
diffuse = rockMat.node_tree.nodes.new(type = 'ShaderNodeBsdfDiffuse')
translucent = rockMat.node_tree.nodes.new(type = 'ShaderNodeBsdfTranslucent')
add = rockMat.node_tree.nodes.new(type = 'ShaderNodeAddShader')
emission = rockMat.node_tree.nodes.new(type = 'ShaderNodeEmission')
tex_coord = rockMat.node_tree.nodes.new(type = 'ShaderNodeTexCoord')
tex_image = rockMat.node_tree.nodes.new(type = 'ShaderNodeTexImage')
mix = rockMat.node_tree.nodes.new(type = 'ShaderNodeMixShader')
output = rockMat.node_tree.nodes.new(type = 'ShaderNodeOutputMaterial')


#Sets emission Color and Strength values
emission.inputs["Color"].default_value = [1.0,0.17,0.075,1.0] #Orange
emission.inputs["Strength"].default_value =5.0


#Sets diffuse Color values
diffuse.inputs["Color"].default_value = [0.3,0.3,0.3,1.0] #Grey


#Sets translucent Color values
translucent.inputs["Color"].default_value = [0.3,0.3,0.3,1.0] #Grey


#Makes links
#rockMat.node_tree.links.new(diffuse.outputs['BSDF'], add.inputs['Shader'])
#rockMat.node_tree.links.new(translucent.outputs['BSDF'], add.inputs['Shader'])
#rockMat.node_tree.links.new(emission.outputs['Emission'], mix.inputs['Shader'])
rockMat.node_tree.links.new(tex_coord.outputs['UV'], tex_image.inputs['Vector'])
rockMat.node_tree.links.new(tex_image.outputs['Color'], mix.inputs['Fac'])
#rockMat.node_tree.links.new(add.outputs['Shader'], mix.inputs['Shader'])
rockMat.node_tree.links.new(mix.outputs['Shader'], output.inputs['Surface'])


#Sets node Location in Node Editor space
diffuse.location = (51, 83)
translucent.location = (89, -84)
add.location = (305, 142)
emission.location = (305, 40)
tex_coord.location = (38, 357)
tex_image.location = (226, 357)
mix.location = (513, 377)
output.location = (721, 379)

Data in blender can be accessed by either name or index in most cases. For example, in the default scene you have 3 objects, Camera, Cube and Lamp.
try it in the console:
>>> bpy.data.objects[0]
bpy.data.objects[‘Camera’]

>>> bpy.data.objects[‘Camera’]
bpy.data.objects[‘Camera’]

You don’t have to “add” indices, index number is given from top to bottom, so in the ADD node, the “Shader” in the top will be index 0, and the one in the bottom index 1.

here:
rockMat.node_tree.links.new(diffuse.outputs[‘BSDF’], add.inputs[0]) #if you want to link with the upper shader input.
rockMat.node_tree.links.new(diffuse.outputs[‘BSDF’], add.inputs[1]) #or the bottom one.

yes, full path, with r for windows slashes. e.g. You should use a try in case the path doesn’t exist or the script will fail


try:
    im = bpy.data.images.load(r"C:	mp	exture	exture.png")
except: 
    pass #or whatever you wanna do.

And one more, will adding the image to the node still have to have something like tex_image.input(enter rest here), or is there something else?

I don’t understand this one.
To create a texture image node and assign a blender image:


loaded_image =  bpy.data.images.load(r"C:	mp	exture	exture.png")
tex_image_node = material.node_tree.nodes.new('ShaderNodeTexImage')
tex_image_node.image = loaded_image


Thanks again for your help! I’ll keep this stuff in mind for future reference. Hopefully, I’ll finish the code shortly. I may put it online for everyone, but I don’t know how useful most will find it. I may have to add some extra things: menu with buttons and sliders, color input, random seed (If I can figure that out).

You’ve been a big help and I really appreciate it!

With the image path, how do I keep it from losing the path? If I add it to a file with the script, someone downloads it, what is going to create a new path for that person’s computer? Or would it be better to add a separate area in the python file with the image data in it (if that’s possible)?

I think you should create a function that has texture path as a parameter.
Later you can also extend it and use a panel/file browser for the UI to get that path.

Alright, I’ll try that out, though I was thinking, is there a way to write a code to trace back to the scripts .py file location to locate a path fragment? Like, if my path was C:\Blender\Script\Texture\Texture.png and the script.py was located in the Script file and the code would say to begin looking in the .py file location and try to find the path with the fragment \Texture\Texture.png

That way even if the script file location changes, it can still locate the texture within the script’s file
Like, if the script file location changed from C:\Blender\Script to C:\Blender\DownloadedFiles\Script or anything like that, it still starts in the Script file and finds the Texture file and the Texture.png within it.

Well, that’s tricky,depending on how you are going to distribute your script. If it’s as an addon (it will be placed in the addons folder) yes you can use relative paths. If not, I’m not very familiar with how the API handles paths, since I observed the scripts are run like if they were run from C:\ (in windows) and not whatever folder you open them with. Better to investigate and open a new thread when you have your script ready to ship.

I think it will be relative paths kinda thing. I will open another thread for this, thank you for all your help!