Skip to content

Instantly share code, notes, and snippets.

@behreajj
Created May 29, 2018 23:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save behreajj/87b0a32fce01e78d3351b6862c797ade to your computer and use it in GitHub Desktop.
Save behreajj/87b0a32fce01e78d3351b6862c797ade to your computer and use it in GitHub Desktop.
Sampler Full
import bpy
from math import cos, sin, fmod
from random import TWOPI
def append_group_inst(nodes, group_name, data=bpy.data, group_creation_func=None, use_fake_user=False, parent=None, custom_color=None):
group = None
try:
group = data.node_groups[group_name]
except IndexError:
if group_creation_func:
group = group_creation_func(data, use_fake_user)
if group:
gp_in = nodes.new('ShaderNodeGroup')
gp_in.node_tree = group
gp_in.name = group_name
gp_in.parent = parent
if custom_color:
gp_in.use_custom_color = True
gp_in.color = custom_color
return gp_in
else:
return None
def append_ring_inst(nodes, data=bpy.data, scene=bpy.context.scene, group_creation_func=None, use_fake_user=False, point={0: (0.0, 0.0, 0.0)}, center_outer={0: (0.5, 0.5, 0.5)}, center_inner={0: (0.5, 0.5, 0.5)}, background={0: (0.0, 0.0, 0.0, 1.0)}, fill={0: (0.214041, 0.214041, 0.214041, 1.0)}, stroke={0: (1.0, 1.0, 1.0, 1.0)}, margin={0: 0.05}, tiles={0: 1}, scale={0: 0.625}, stroke_weight={0: 0.05}, parent=None, custom_color=None):
ring_inst = append_group_inst(nodes=nodes, group_name='Ring', data=data, group_creation_func=group_creation_func, use_fake_user=use_fake_user, parent=parent, custom_color=custom_color)
anim_node_inputs(input_ref=ring_inst.inputs, args={
'Point': point,
'Center Outer': center_outer,
'Center Inner': center_inner,
'Background': background,
'Fill': fill,
'Stroke': stroke,
'Margin': margin,
'Tiles': tiles,
'Scale': scale,
'Stroke Weight': stroke_weight
}, scene=scene)
return ring_inst
def append_absorb_inst(nodes, data=bpy.data, scene=bpy.context.scene, group_creation_func=None, use_fake_user=False, colors={0: (0.02, 0.02, 0.02, 1.0)}, densities={0: 12.0}, factors={0: 0.5}, lowers={0: 0.0}, uppers={0: 1.0}, parent=None, custom_color=None):
absorb_inst = append_group_inst(nodes=nodes, group_name='Shaped Absorb', data=data, group_creation_func=group_creation_func, use_fake_user=use_fake_user, parent=parent, custom_color=custom_color)
anim_node_inputs(input_ref=absorb_inst.inputs, args={
'Color': colors,
'Density': densities,
'Fac': factors,
'Lower': lowers,
'Upper': uppers}, scene=scene)
return absorb_inst
def append_light_inst(nodes, data=bpy.data, scene=bpy.context.scene, group_creation_func=None, use_fake_user=False, temps={0: 1500.0}, strengths={0: 1.0}, factors={0: 0.5}, lowers={0: 0.0}, uppers={0: 1.0}, parent=None, custom_color=None):
light_inst = append_group_inst(nodes=nodes, group_name='Shaped Light', data=data, group_creation_func=group_creation_func, use_fake_user=use_fake_user, parent=parent, custom_color=custom_color)
anim_node_inputs(input_ref=light_inst.inputs, args={
'Temperature': temps,
'Strength': strengths,
'Fac': factors,
'Lower': lowers,
'Upper': uppers}, scene=scene)
return light_inst
def append_map_inst(nodes, data=bpy.data, scene=bpy.context.scene, group_creation_func=None, use_fake_user=False, origin_value={0: 0.0}, origin_lb={0: -1.0}, origin_ub={0: 1.0}, target_lb={0: 0.0}, target_ub={0: 1.0}, parent=None, custom_color=None):
map_inst = append_group_inst(nodes=nodes, group_name='Map', data=data, group_creation_func=group_creation_func, use_fake_user=use_fake_user, parent=parent, custom_color=custom_color)
anim_node_inputs(input_ref=map_inst.inputs, args={
'Origin Value': origin_value,
'Origin Lower Bound': origin_lb,
'Origin Upper Bound': origin_ub,
'Target Lower Bound': target_lb,
'Target Upper Bound': target_ub,
}, scene=scene)
return map_inst
def append_rotate_z_inst(nodes, data=bpy.data, scene=bpy.context.scene, group_creation_func=None, use_fake_user=False, radians={0: 0.0}, vector={0: (0.0, 0.0, 0.0)}, pivot={0: (0.0, 0.0, 0.0)}, parent=None, custom_color=None):
rotate_z_inst = append_group_inst(nodes=nodes, group_name='RotateZ', data=data, group_creation_func=group_creation_func, use_fake_user=use_fake_user, parent=parent, custom_color=custom_color)
anim_node_inputs(input_ref=rotate_z_inst.inputs, args={
'Radians': radians,
'Vector': vector,
'Pivot': pivot
}, scene=scene)
return rotate_z_inst
def append_txtr_coord(nodes, obj=None, parent=None, custom_color=None):
txtr_coord = nodes.new('ShaderNodeTexCoord')
txtr_coord.object = obj
txtr_coord.parent = parent
if custom_color:
txtr_coord.use_custom_color = True
txtr_coord.color = custom_color
return txtr_coord
def float_lerp(a=0.0, b=1.0, t=0.5):
return (1.0 - t) * a + t * b
def anim_node_inputs(input_ref, args, scene=bpy.context.scene):
# Gather list of entries from args dictionary.
args_entries = args.items()
for arg_entry in args_entries:
name = arg_entry[0] # Key
frames = arg_entry[1] # Value
# Attempt to get the appropriate socket by name.
in_socket = input_ref.get(name)
if in_socket:
# Gather list of entries from frames dictionary.
frame_entries = frames.items()
length = len(frame_entries)
for frame_entry in frame_entries:
frame = frame_entry[0] # Key
default = frame_entry[1] # Value
scene.frame_current = frame
in_socket.default_value = default
if length > 1:
in_socket.keyframe_insert(data_path='default_value')
return input_ref
def append_vec_mult_inst(nodes, data=bpy.data, scene=bpy.context.scene, group_creation_func=None, use_fake_user=False, vector={0: (0.0, 0.0, 0.0)}, scalar={0: 1.0}, parent=None, custom_color=None):
vec_mult_inst = append_group_inst(nodes=nodes, group_name='Vector Mult', data=data, group_creation_func=group_creation_func, use_fake_user=use_fake_user, parent=parent, custom_color=custom_color)
anim_node_inputs(input_ref=vec_mult_inst, args={'Vector': vector, 'Scalar': scalar}, scene=scene)
return vec_mult_inst
def append_noise_txtr(nodes, scene=bpy.context.scene, scales={0: 5.0}, details={0: 2.0}, distortions={0: 0.0}, parent=None, custom_color=None):
noise_txtr = nodes.new('ShaderNodeTexNoise')
noise_txtr.parent = parent
if custom_color:
noise_txtr.use_custom_color = True
noise_txtr.color = custom_color
anim_node_inputs(input_ref=noise_txtr.inputs, args={'Scale': scales, 'Detail': details, 'Distortion': distortions}, scene=scene)
return noise_txtr
def append_voronoi_txtr(nodes, scene=bpy.context.scene, coloring='INTENSITY', scales={0: 5.0}, parent=None, custom_color=None):
vor_txtr = nodes.new('ShaderNodeTexVoronoi')
vor_txtr.coloring = coloring
vor_txtr.parent = parent
if custom_color:
vor_txtr.use_custom_color = True
vor_txtr.color = custom_color
anim_node_inputs(input_ref=vor_txtr.inputs, args={'Scale': scales}, scene=scene)
return vor_txtr
def append_comb_hsv(nodes, scene=bpy.context.scene, h={0: 0.0}, s={0: 0.0}, v={0: 1.0}, parent=None, custom_color=None):
hsv = nodes.new('ShaderNodeCombineHSV')
hsv.parent = parent
if custom_color:
hsv.use_custom_color = True
hsv.color = custom_color
anim_node_inputs(input_ref=hsv.inputs, args={'H': h, 'S': s, 'V': v}, scene=scene)
return hsv
def append_pbr_node(nodes, scene=bpy.context.scene, distribution='MULTI_GGX', base_color={0: (1.0, 1.0, 1.0, 1.0)}, subsurface={0: 0.0}, subsurface_radius={0: (1.0, 1.0, 1.0)}, subsurface_color={0: (0.7, 0.1, 0.1, 1.0)}, metallic={0: 0.0}, specular={0: 0.5}, specular_tint={0: 0.0}, roughness={0: 0.5}, anisotropic={0: 0.0}, anisotropic_rotation={0: 0.0}, sheen={0: 0.0}, sheen_tint={0: 0.5}, clearcoat={0: 0.0}, clearcoat_roughness={0: 0.3}, ior={0: 1.45}, transmission={0: 0.0}, parent=None, custom_color=None):
pbr = nodes.new('ShaderNodeBsdfPrincipled')
pbr.parent = parent
if custom_color:
pbr.use_custom_color = True
pbr.color = custom_color
# Set distribution. Choices are 'GGX' and 'MULTI_GGX'.
pbr.distribution = distribution
anim_node_inputs(input_ref=pbr.inputs, args={
'Base Color': base_color,
'Subsurface': subsurface,
'Subsurface Radius': subsurface_radius,
'Subsurface Color': subsurface_color,
'Metallic': metallic,
'Specular': specular,
'Specular Tint': specular_tint,
'Roughness': roughness,
'Anisotropic': anisotropic,
'Anisotropic Rotation': anisotropic_rotation,
'Sheen': sheen,
'Sheen Tint': sheen_tint,
'Clearcoat': clearcoat,
'Clearcoat Roughness': clearcoat_roughness,
'IOR': ior,
'Transmission': transmission}, scene=scene)
return pbr
def init_pbr_mat(data=bpy.data, scene=bpy.context.scene, obj=bpy.context.active_object, name='Material', distribution='MULTI_GGX', base_color={0: (1.0, 1.0, 1.0, 1.0)}, subsurface={0: 0.0}, subsurface_radius={0: (1.0, 1.0, 1.0)}, subsurface_color={0: (0.7, 0.1, 0.1, 1.0)}, metallic={0: 0.0}, specular={0: 0.5}, specular_tint={0: 0.0}, roughness={0: 0.5}, anisotropic={0: 0.0}, anisotropic_rotation={0: 0.0}, sheen={0: 0.0}, sheen_tint={0: 0.5}, clearcoat={0: 0.0}, clearcoat_roughness={0: 0.3}, ior={0: 1.45}, transmission={0: 0.0}):
material = data.materials.new(name)
material.use_nodes = True
node_tree = material.node_tree
nodes = node_tree.nodes
# Acquire material output. If not present, create a new one.
mat_output = None
try:
mat_output = nodes['Material Output']
except IndexError:
mat_output = nodes.new('ShaderNodeOutputMaterial')
# Delete diffuse BSDF if present.
try:
nodes.remove(nodes['Diffuse BSDF'])
except IndexError:
pass
# Add geometry and PBR shaders.
geom = nodes.new('ShaderNodeNewGeometry')
pbr = append_pbr_node(nodes=nodes, scene=scene, distribution=distribution, base_color=base_color, subsurface=subsurface, subsurface_radius=subsurface_radius, subsurface_color=subsurface_color, metallic=metallic, specular=specular, specular_tint=specular_tint, roughness=roughness, anisotropic=anisotropic, anisotropic_rotation=anisotropic_rotation, sheen=sheen, sheen_tint=sheen_tint, clearcoat=clearcoat, clearcoat_roughness=clearcoat_roughness, ior=ior, transmission=transmission)
# Link geometry and PBR shaders.
pbr_inputs = pbr.inputs
geom_outputs = geom.outputs
node_tree.links.new(pbr_inputs['Normal'], geom_outputs['Normal'])
node_tree.links.new(pbr_inputs['Clearcoat Normal'], geom_outputs['Normal'])
node_tree.links.new(pbr_inputs['Tangent'], geom_outputs['Tangent'])
node_tree.links.new(mat_output.inputs['Surface'], pbr.outputs['BSDF'])
return material
def create_sampler(data=bpy.data, context=bpy.context, ops=bpy.ops, scene=bpy.context.scene, loc=(0.0, 0.0, 0.0), name='Sampler', count=20, swatch_size=1.0, segments=48, ring_count=24, frame_start=bpy.context.scene.frame_start, frame_end=bpy.context.scene.frame_end):
ops.object.empty_add(type='PLAIN_AXES', location=loc)
sampler = context.object
sampler.name = name
inv_count_ln = 1.0 / (count - 1)
inv_count_rd = 1.0 / count
radius = count * 0.35
for i in range(0, count, 1):
# Enter edit mode upon creation.
ops.mesh.primitive_uv_sphere_add(size=swatch_size, segments=segments, ring_count=ring_count, enter_editmode=True)
# Set UV coordinates.
ops.uv.sphere_project(direction='ALIGN_TO_OBJECT')
# Toggle back to object mode.
ops.object.editmode_toggle()
# Use smooth shading.
ops.object.shade_smooth()
# Set name and parent.
obj = context.object
obj.name = obj.data.name = '{0:0>3d}.Sample'.format(i)
obj.parent = sampler
# Set location.
i_percent_rd = i * inv_count_rd
theta = TWOPI * i_percent_rd
obj.location = (cos(theta) * radius, sin(theta) * radius, 0.0)
# Create materials.
mat = init_pbr_mat(obj=obj, name=obj.name, metallic={frame_start: 0.0, frame_end: 0.5}, specular={0: 0.2}, roughness={frame_start: 0.01, frame_end: 0.333}, sheen={0: 0.2}, clearcoat={0: 0.99}, clearcoat_roughness={0: 0.01})
# Cache references.
node_tree = mat.node_tree
nodes = node_tree.nodes
# Set color.
i_percent_ln = i * inv_count_ln
comb_hsv = append_comb_hsv(nodes=nodes, scene=scene, h={0: float_lerp(1.0, 0.5, i_percent_ln)}, s={0: 1.0}, v={frame_start: 1.0, frame_end: 0.5})
ring_inst = append_ring_inst(nodes=nodes, data=data, scene=scene, fill={0: (1.0, 0.95, 0.9, 1.0)}, stroke={0: (1.0, 0.75, 0.667, 1.0)}, tiles={0: 4}, margin={0: 0.1})
rtz_inst = append_rotate_z_inst(nodes=nodes, data=data, scene=scene, radians={frame_start: theta, frame_end: theta + TWOPI}, pivot={0: (0.5, 0.5, 0.5)})
txtr_coord = append_txtr_coord(nodes=nodes, obj=obj)
node_tree.links.new(nodes['Principled BSDF'].inputs['Base Color'], ring_inst.outputs['Stroke Fill'])
node_tree.links.new(nodes['Principled BSDF'].inputs['Roughness'], ring_inst.outputs[3])
node_tree.links.new(nodes['Principled BSDF'].inputs['Metallic'], ring_inst.outputs[3])
node_tree.links.new(nodes['Material Output'].inputs['Displacement'], ring_inst.outputs[3])
node_tree.links.new(nodes['Principled BSDF'].inputs['Transmission'], ring_inst.outputs[4])
node_tree.links.new(ring_inst.inputs['Background'], comb_hsv.outputs['Color'])
node_tree.links.new(ring_inst.inputs['Point'], rtz_inst.outputs['Vector'])
node_tree.links.new(rtz_inst.inputs['Vector'], txtr_coord.outputs['Generated'])
absorb_hsv = append_comb_hsv(nodes=nodes, scene=scene, h={0: float_lerp(1.0, 0.5, i_percent_ln)}, s={0: 1.0}, v={frame_start: 0.25, frame_end: 0.0})
light_inst = append_light_inst(nodes=nodes, data=data, scene=scene, lowers={0: -.01}, uppers={0: 1.01})
absorb_inst = append_absorb_inst(nodes=nodes, data=data, scene=scene, lowers={0: -.01}, uppers={0: 1.01}, densities={frame_start: 3.0, frame_end: 6.0})
map_inst = append_map_inst(nodes=nodes, data=data, scene=scene, origin_lb={0: 0.0}, origin_ub={0: 1.0}, target_lb={0: float_lerp(800.0, 1500.0, i_percent_ln)}, target_ub={0: float_lerp(2000.0, 6000.0, i_percent_ln)})
add_sh = nodes.new('ShaderNodeAddShader')
sep_xyz = nodes.new('ShaderNodeSeparateXYZ')
node_tree.links.new(sep_xyz.inputs['Vector'], rtz_inst.outputs['Vector'])
node_tree.links.new(map_inst.inputs['Origin Value'], sep_xyz.outputs['X'])
node_tree.links.new(light_inst.inputs['Temperature'], map_inst.outputs['Target Value'])
node_tree.links.new(add_sh.inputs[0], absorb_inst.outputs['Volume'])
node_tree.links.new(add_sh.inputs[1], light_inst.outputs['Emission'])
node_tree.links.new(nodes['Material Output'].inputs['Volume'], add_sh.outputs['Shader'])
node_tree.links.new(light_inst.inputs['Fac'], ring_inst.outputs[3])
node_tree.links.new(absorb_inst.inputs['Fac'], ring_inst.outputs[4])
node_tree.links.new(absorb_inst.inputs['Color'], absorb_hsv.outputs['Color'])
# NEW
noise = append_noise_txtr(nodes=nodes, scene=scene, scales={0: float_lerp(1.5, 3.0, i_percent_ln)}, details={0: float_lerp(2.0, 1.5, i_percent_ln)}, distortions={frame_start: 0.0, frame_end: 1.0})
vor = append_voronoi_txtr(nodes=nodes, scene=scene, scales={0: float_lerp(5.0, 2.0, i_percent_ln)})
vec_mult = append_vec_mult_inst(nodes=nodes, data=data, scene=scene)
node_tree.links.new(ring_inst.inputs['Point'], vec_mult.outputs['Vector'])
node_tree.links.new(vec_mult.inputs['Vector'], rtz_inst.outputs['Vector'])
node_tree.links.new(vec_mult.inputs['Scalar'], vor.outputs['Fac'])
node_tree.links.new(vor.inputs['Scale'], noise.outputs['Fac'])
node_tree.links.new(vor.inputs['Vector'], rtz_inst.outputs['Vector'])
node_tree.links.new(noise.inputs['Vector'], nodes['Geometry'].outputs['Position'])
# Append new material to sample.
obj.data.materials.append(mat)
return sampler
create_sampler()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment