2
$\begingroup$

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

    oo.modifier_add(type='BEVEL')
    ao.modifiers["Bevel"].width = 0.3
    ao.modifiers["Bevel"].segments = 3
    oo.shade_smooth()

    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)
$\endgroup$
12
  • $\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
2
$\begingroup$

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'
$\endgroup$
4
  • $\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

This site is temporarily in read only mode and not accepting new answers.

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