# is there a way to streamline scripting these shaders and modifier keyframes?

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:

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')

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

b = mat_output.inputs['Surface']

a = diffuse.outputs['BSDF']
b =     mix.inputs[1]  # 'Shader' is used twice, so you access via index.

a = emit.outputs['Emission']
b =  mix.inputs[2]

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 location == None:
location = np.array([6,6,4])*(np.random.random(3)+np.array([-0.5, -0.5, +0.25]))

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)

• can you be specific? that's just a code dump – zeffii Jul 27 '15 at 13:43
• 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. – uhoh Jul 27 '15 at 13:47
• I'm assuming I shouldn't be looking at script nodes here, since I am using a script to create many materials. – uhoh Jul 27 '15 at 13:49
• script nodes in Cycles are currently for OSL code only (at least, I don't know of any other uses) – zeffii Jul 27 '15 at 14:04
• – 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.

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 = [
'DOT_0|Value > ColorRamp|Fac',
'ColorRamp|Color > Diffuse BSDF|Color'
]



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

a = Geometry_0.outputs['Position']


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