Here is a simple demo script for keyframes on shaders and modifiers for multiple objects. In the future there will be much more going on, so I want to know if there is an easier way to do these kinds of things, or is all this scripting pretty much the right approach?

For example, near the end: ee = cube.active_material.node_tree.nodes.get("Emission") what would I do if there were more than one Emission node? Can I name them? (there could be 100 materials) I wouldn't want to have to keep track of the order they are added if I can help it.

This is what this sample script does:

enter image description here

Scripting the nodes is modified from @zeffii here

def make_a_glo_mat(color_diff=None, color_emit=None, 
                   emit_strength=None, frac=None):

# modified from HERE: https://blender.stackexchange.com/a/32162/5334

    glo_mat = bpy.data.materials.new('Glow_mat')
    glo_mat.use_nodes = True
    nodes = glo_mat.node_tree.nodes

    # material output and diffuse nodes are present already, get them by name
    mat_output = nodes.get("Material Output")
    diffuse = nodes.get("Diffuse BSDF") # new(type='ShaderNodeBsdfDiffuse')

    mix  = nodes.new(type='ShaderNodeMixShader')
    emit = nodes.new(type='ShaderNodeEmission')

    mat_output.location =  300,    0
    mix.location        =    0,    0
    diffuse.location    = -200,   50
    emit.location       = -200, -100

    # link mix_shader to material output
    a =        mix.outputs['Shader']
    b = mat_output.inputs['Surface']
    glo_mat.node_tree.links.new(a, b)

    # link diffuse to mix
    a = diffuse.outputs['BSDF']
    b =     mix.inputs[1]  # 'Shader' is used twice, so you access via index.
    glo_mat.node_tree.links.new(a, b)

    # link emissive to mix
    a = emit.outputs['Emission']
    b =  mix.inputs[2]
    glo_mat.node_tree.links.new(a, b)

    if color_diff == None:
        color_diff = [0.8, 0.0, 0.2, 1.0]
    if color_emit == None:
        color_emit = [0.2, 0.8, 0.8, 1.0]
    if frac == None:
        frac = 0.5
    if emit_strength == None:
        emit_strength = 0.5

    mix.inputs[0].default_value = frac  # factor
    diffuse.inputs['Color'].default_value = color_diff
    emit.inputs['Color'].default_value    = color_emit
    emit.inputs['Strength'].default_value = emit_strength

    return glo_mat

This I wrote...

def make_funky_cube(radius=None, location=None):

    if radius == None:
        radius = 1.0
    if location == None:
        location = np.array([6,6,4])*(np.random.random(3)+np.array([-0.5, -0.5, +0.25]))

    ok = bpy.ops.mesh.primitive_cube_add(radius=radius, location=location)

    oo = bpy.ops.object
    ao = bpy.context.active_object

    ao.modifiers["Bevel"].width = 0.3
    ao.modifiers["Bevel"].segments = 3

    return ao

And this now works thanks to this from @zeffii and this from @sambler .

import bpy
import numpy as np
scene = bpy.context.scene
scene.render.engine = 'CYCLES'
f_zero = np.array([[13, 22], [53, 62]])
sigma  = np.array([[4, 10], [4, 10]])
n_frames = 80
# make materials in Nodes (black is blue for now)
glo_red_mat = make_a_glo_mat(color_diff=[0.8, 0.2, 0.2, 1.0], 
                             color_emit=[1.0, 1.0, 1.0, 1.0], 
                             emit_strength=1.0, frac=0.5)
glo_blk_mat = make_a_glo_mat(color_diff=[0.0, 0.0, 0.5, 1.0], 
                             color_emit=[1.0, 1.0, 1.0, 1.0], 
                             emit_strength=1.0, frac=0.5)
# make two cubes 
red_cube   = make_funky_cube(radius=1.0, location=(-2.0, 0.0 ,2.0))
black_cube = make_funky_cube(radius=1.0, location=(+2.0, 0.0, 2.0))
red_cube.active_material   = glo_red_mat
black_cube.active_material = glo_blk_mat

bpy.context.scene.frame_end = n_frames
for i_frame in range(n_frames):
    a = np.exp(-(i_frame - f_zero)**2 / (2.*sigma**2))
    cubes = [red_cube, black_cube]
    things = a[0], a[1]
    for thing, cube in zip(things, cubes):
        s  = 1.00 + 0.40 * thing[0]
        bw = 0.05 + 0.25 * thing[0]
        e  = 0.20 + 0.80 * thing[1]
# got help HERE: https://blender.stackexchange.com/a/10574/5334
        cube.modifiers["Bevel"].width = bw
        cube.scale = (s, s, s)
        cube.keyframe_insert(data_path='scale', frame = i_frame + 1, index=-1)
        cube.keyframe_insert(data_path='modifiers["Bevel"].width', frame = i_frame + 1, index=-1)
        ee = cube.active_material.node_tree.nodes.get("Emission")
        ee.inputs[1].default_value = e
        ee.inputs[1].keyframe_insert(data_path="default_value", frame=i_frame+1)
  • $\begingroup$ can you be specific? that's just a code dump $\endgroup$
    – zeffii
    Jul 27 '15 at 13:43
  • $\begingroup$ I've added a "For example", but it is not "just" a code dump. It's a procedure, and my larger question is, am I way off, or is this size/complexity of scripting basically what it's going to be like going forward. Is there something easier that I just don't know about. $\endgroup$
    – uhoh
    Jul 27 '15 at 13:47
  • $\begingroup$ I'm assuming I shouldn't be looking at script nodes here, since I am using a script to create many materials. $\endgroup$
    – uhoh
    Jul 27 '15 at 13:49
  • $\begingroup$ script nodes in Cycles are currently for OSL code only (at least, I don't know of any other uses) $\endgroup$
    – zeffii
    Jul 27 '15 at 14:04
  • 1
    $\begingroup$ related: blender.stackexchange.com/questions/29007/… $\endgroup$
    – p2or
    Jul 27 '15 at 16:09

This borders on objective/subjective terrain, but there's facts!

You can fully script shaders, and indeed there are scenarios where it takes a lot less time to write a script to take care of work which might otherwise be very tedious.

But there's nothing stopping you from first tweaking your shader in a .blend file and then importing ( bpy.ops.wm.append) it later in your new projects and directly adjusting specific node values only via a script.

There is no 'better', it's a choice of weighing the advantages to either approach.

Linking Nodes

Regarding specifically the scripting of shaders, if it's something you intend to do a lot then it might be worth using a 'connection grammar', (essentially a larger function which you can import and forget about) which takes care of the tedious node linking.

It looks something like this (4 links!)

# using connection grammar    
connection_list = [
    'Geometry_0|Position > ADD_0|0',
    'ADD_0|Vector > DOT_0|0',
    'DOT_0|Value > ColorRamp|Fac',
    'ColorRamp|Color > Diffuse BSDF|Color'

for link in connection_list:
    make_link(mat, link)

as opposed to something like (for just one link!):

a = Geometry_0.outputs['Position']
b = ADD_0.inputs[0]
glo_mat.node_tree.links.new(a, b)

Adding modifiers

can also be done a different way, for instance here using a screw modifier, using a reference to the object. (another example of .ops avoidance)

ao = bpy.context.active_object
screw = ao.modifiers.new(type='SCREW', name="screw")
screw.axis = 'X'
  • $\begingroup$ woa! That's really something to look into! (have started to look here). Ya I'm not looking for "better", but just want to know if there are "other" ways I don't know about. Thanks @zeffii $\endgroup$
    – uhoh
    Jul 27 '15 at 14:00
  • $\begingroup$ Great! I will have to take that apart in the morning after coffee. Thanks! $\endgroup$
    – uhoh
    Jul 27 '15 at 14:02
  • $\begingroup$ I don't think a lot of people script Cycles shaders, not from scratch anyway.. but there's nothing stopping us from picking best of both worlds. $\endgroup$
    – zeffii
    Jul 27 '15 at 14:02
  • $\begingroup$ i've updated the last bit $\endgroup$
    – zeffii
    Jul 27 '15 at 14:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.