Files
Halloween/tools/render_examples_v3.py
T

133 lines
9.5 KiB
Python

"""Improve the existing concept scene; render still examples only."""
import bpy, math, random, os
from mathutils import Vector
ROOT=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
OUT=os.path.join(ROOT,'preview','v3');os.makedirs(OUT,exist_ok=True)
bpy.ops.wm.open_mainfile(filepath=os.path.join(ROOT,'preview','halloween_preview.blend'))
s=bpy.context.scene;s.eevee.taa_render_samples=32
random.seed(104)
def material(name,c,rough=.8):
m=bpy.data.materials.new(name);m.use_nodes=True
p=m.node_tree.nodes.get('Principled BSDF');p.inputs['Base Color'].default_value=(*c,1);p.inputs['Roughness'].default_value=rough
return m
leafm=[material('Dry oak leaves '+str(i),c) for i,c in enumerate([(.20,.055,.012),(.32,.14,.032),(.10,.042,.013),(.40,.23,.075),(.16,.08,.025)])]
shroud=bpy.data.objects['Breathing burial shroud'];base=shroud.data.shape_keys.key_blocks['Rest'];breath=shroud.data.shape_keys.key_blocks['Breath']
# A real opening at the hand's emergence point, rather than intersecting cloth.
import bmesh
bm=bmesh.new();bm.from_mesh(shroud.data)
for face in list(bm.faces):
c=face.calc_center_median()
if abs(c.x)<.16 and abs(c.y)<.18:bm.faces.remove(face)
bm.to_mesh(shroud.data);bm.free()
# Leaf litter follows the animated cloth with its own synchronized shape key.
verts=[];faces=[];indices=[];lifts=[]
def surface(x,y):
u=(x+.8)/1.6*28;v=(y+1.35)/2.7*44;i=min(27,max(0,int(u)));j=min(43,max(0,int(v)));a=u-i;b=v-j
zs=[base.data[k].co.z for k in [j*29+i,j*29+i+1,(j+1)*29+i,(j+1)*29+i+1]]
return zs[0]*(1-a)*(1-b)+zs[1]*a*(1-b)+zs[2]*(1-a)*b+zs[3]*a*b
for n in range(950):
x=random.uniform(-.77,.77);y=random.uniform(-1.32,1.32)
if x*x+y*y<.24**2:continue
angle=random.random()*math.tau;size=random.uniform(.045,.105);start=len(verts)
z=surface(x,y)+random.uniform(.012,.045)
coords=[(0,0,.010)]
for k in range(14):
a=k*math.tau/14;r=1 if k%2==0 else .65
coords.append((math.cos(a)*size*r,math.sin(a)*size*.55*r,random.uniform(-.004,.014)))
lift=.23*math.exp(-((x/.50)**2+(y/.85)**2))
for xx,yy,zz in coords:
verts.append((x+xx*math.cos(angle)-yy*math.sin(angle),y+xx*math.sin(angle)+yy*math.cos(angle),z+zz));lifts.append(lift)
idx=random.randrange(len(leafm))
for k in range(14):faces.append((start,start+1+k,start+1+(k+1)%14));indices.append(idx)
me=bpy.data.meshes.new('Curled oak leaf litter');me.from_pydata(verts,[],faces);me.update()
leaves=bpy.data.objects.new('Leaves following breathing surface',me);s.collection.objects.link(leaves)
for m in leafm:me.materials.append(m)
for p,idx in zip(me.polygons,indices):p.material_index=idx
leaves.shape_key_add(name='Rest');lk=leaves.shape_key_add(name='Breath')
for v,lift in zip(lk.data,lifts):v.co.z+=lift
for frame in range(1,481):
s.frame_set(frame);lk.value=breath.value;lk.keyframe_insert('value',frame=frame)
# A layer covering the middle opening bursts upward with the emerging hand.
for n in range(110):
angle=random.uniform(0,math.tau);radius=.23*math.sqrt(random.random())
x=radius*math.cos(angle);y=radius*math.sin(angle);z0=surface(x,y)+.035
size=random.uniform(.055,.10);vs=[(0,0,.009)]
for k in range(14):
theta=k*math.tau/14;r=1 if k%2==0 else .65
vs.append((size*r*math.cos(theta),size*.55*r*math.sin(theta),random.uniform(-.004,.012)))
me=bpy.data.meshes.new('Flying curled leaf');me.from_pydata(vs,[],[(0,k+1,(k+1)%14+1) for k in range(14)]);me.update()
obj=bpy.data.objects.new('Burst leaf %03d'%n,me);s.collection.objects.link(obj);obj.data.materials.append(random.choice(leafm))
delay=random.uniform(0,.11);speed=random.uniform(.35,.85);up=random.uniform(2.0,3.0);spin=random.uniform(-7,7);initial=random.uniform(0,math.tau)
for frame in [1,200,310]+list(range(311,371))+[480]:
s.frame_set(frame);t=max(0,(frame-313)/24-delay)
flight=min(t,2*up/5.4);direction=angle+.8*flight
xx=x+speed*flight*math.cos(direction);yy=y+speed*flight*math.sin(direction)
zz=z0+up*flight-2.7*flight*flight
if t==0:zz+=.23*math.exp(-((x/.50)**2+(y/.85)**2))*breath.value
else:zz=max(.025,zz)
obj.location=(xx,yy,zz);obj.rotation_euler=(spin*flight,.7*spin*flight,initial+spin*flight)
obj.keyframe_insert('location',frame=frame);obj.keyframe_insert('rotation_euler',frame=frame)
for fc in obj.animation_data.action.fcurves:
for key in fc.keyframe_points:key.interpolation='LINEAR'
hand=bpy.data.objects['Emerging hand'];hand.animation_data_clear();hand.location=(0,0,-.65)
for frame,z in [(1,-.65),(310,-.65),(314,-.35),(322,.15),(340,.15),(365,-.65),(480,-.65)]:
hand.location.z=z;hand.keyframe_insert('location',frame=frame)
# Dimmer porch and blue moonlight, red glow beneath the grave.
bpy.data.lights['Moon'].energy=220;bpy.data.lights['Porch'].energy=95;bpy.data.lights['Grave blue'].energy=95
red=bpy.data.lights.new('Hidden red glow','POINT');red.energy=32;red.color=(1,.018,.004);red.shadow_soft_size=.28
ob=bpy.data.objects.new('Hidden red glow',red);s.collection.objects.link(ob);ob.location=(0,-.3,.6)
# Bare branches create recognisable long, eerie shadows.
bark=material('Dark tree bark',(.025,.018,.014))
def rod(name,a,b,r,m,parent=None):
d=Vector(b)-Vector(a);bpy.ops.mesh.primitive_cylinder_add(vertices=10,radius=r,depth=d.length,location=(Vector(a)+Vector(b))/2);o=bpy.context.object;o.name=name;o.rotation_euler=d.to_track_quat('Z','Y').to_euler();o.data.materials.append(m)
if parent:o.parent=parent
return o
for x,y in [(-3.3,2.5),(-4,-1.5)]:
rod('Bare tree trunk',(x,y,0),(x+.1,y,3.8),.11,bark)
for i in range(8):
h=random.uniform(1.7,3.4);tip=(x+random.uniform(-1.5,1.5),y+random.uniform(-.8,.8),h+random.uniform(.3,1))
rod('Bare branch',(x,y,h),tip,.035,bark)
rod('Branch twig',tip,(tip[0]+.4,tip[1]-.2,tip[2]+.4),.012,bark)
# Thin ground mist; faces remain readable.
fog=bpy.data.materials.new('Thin ground mist');fog.use_nodes=True;nodes=fog.node_tree.nodes;nodes.clear();out=nodes.new('ShaderNodeOutputMaterial');vol=nodes.new('ShaderNodeVolumePrincipled');vol.inputs['Density'].default_value=.025;vol.inputs['Color'].default_value=(.45,.55,.65,1);fog.node_tree.links.new(vol.outputs['Volume'],out.inputs['Volume'])
bpy.ops.mesh.primitive_cube_add(size=1,location=(0,0,.22));ob=bpy.context.object;ob.name='Low garden mist';ob.scale=(10,10,.40);ob.data.materials.append(fog)
black=bpy.data.materials['Dark fabric']
# Replace floating boots with jointed, stepping legs and swinging sleeves.
roots=[bpy.data.objects[n] for n in ['Adult A','Adult B','Child witch','Child vampire']]
for root,h in zip(roots,[1.78,1.68,1.05,1.2]):
for ob in list(root.children):
if ob.name.startswith('Boot'):bpy.data.objects.remove(ob,do_unlink=True)
gate='((frame>=95 and frame<165) or (frame>=311 and frame<=344))'
for side in [-1,1]:
phase=0 if side<0 else math.pi
hip=bpy.data.objects.new(root.name+' leg pivot '+str(side),None);s.collection.objects.link(hip);hip.parent=root;hip.location=(side*h*.075,0,h*.40)
upper=rod('Trouser thigh',(0,0,0),(0,0,-h*.17),h*.041,black,hip)
knee=bpy.data.objects.new(root.name+' knee '+str(side),None);s.collection.objects.link(knee);knee.parent=hip;knee.location=(0,0,-h*.17)
rod('Trouser shin',(0,0,0),(0,0,-h*.16),h*.036,black,knee)
bpy.ops.mesh.primitive_uv_sphere_add(segments=16,ring_count=8,location=(0,.025,-h*.19));boot=bpy.context.object;boot.name='Animated boot';boot.parent=knee;boot.scale=(h*.055,h*.10,h*.045);boot.data.materials.append(black)
hip.driver_add('rotation_euler',0).driver.expression=f'0.40*sin(frame*0.34+{phase}) if {gate} else 0'
knee.driver_add('rotation_euler',0).driver.expression=f'-0.40*max(0,-sin(frame*0.34+{phase})) if {gate} else 0'
for idx,ob in enumerate([o for o in root.children if o.name.startswith('Sleeve')]):
ob.driver_add('rotation_euler',0).driver.expression=f'0.20*sin(frame*0.34+{idx*math.pi}) if {gate} else 0.1'
# Keep the family at the grave for the fictional fright scene.
root.animation_data_clear()
child=root.name.startswith('Child');idx=roots.index(root)
x=[3.25,3.9,2.10,2.82][idx];y=[.05,.8,-.55,-1.0][idx]
positions=[(1,(x,-9-idx*.35,0)),(95,(x,-5-idx*.25,0)),(165,(x,y,0)),(310,(x,y,0)),(325,(x+.20,y-.30,0)),(345,(x+.22,y-.40,0)),(480,(x+.22,y-.4,0))]
for f,pos in positions:root.location=(pos[0],pos[1],pos[2]+.065);root.keyframe_insert('location',frame=f)
for f,ang in [(1,0),(165,-.15),(310,-.15),(321,.18 if child else .07),(345,0),(480,0)]:root.rotation_euler.z=ang;root.keyframe_insert('rotation_euler',frame=f)
root.driver_add('rotation_euler',0).driver.expression='-0.14 if 316<=frame<=328 else 0'
# Camera remains with the family; no later jump to an adults-only sequence.
cam=s.camera;cam.animation_data_clear();cam.data.lens=40
for f,loc,target in [(1,(7.5,-9,4),(.7,0,.6)),(165,(6.8,-7.6,3.3),(.7,0,.65)),(310,(6.8,-7.6,3.3),(.7,0,.65)),(320,(6.6,-7.5,3.15),(.7,0,.65)),(480,(7.5,-9,4),(.7,0,.6))]:
cam.location=loc;cam.rotation_euler=(Vector(target)-cam.location).to_track_quat('-Z','Y').to_euler();cam.keyframe_insert('location',frame=f);cam.keyframe_insert('rotation_euler',frame=f)
# Warm reveal is brief and darker than the previous white flash.
flash=bpy.data.lights['Brief reveal'];flash.animation_data_clear();flash.color=(1,.24,.07)
for f,power in [(1,0),(310,0),(316,350),(322,65),(332,140),(340,0),(480,0)]:flash.energy=power;flash.keyframe_insert('energy',frame=f)
s.frame_set(200);s.render.image_settings.file_format='PNG';s.render.filepath=os.path.join(OUT,'example_')
bpy.ops.file.pack_all();bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'halloween_examples.blend'))
for frame in [310,322,334]:
s.frame_set(frame);s.render.filepath=os.path.join(OUT,'example_%03d.png'%frame);bpy.ops.render.render(write_still=True)
print('V3 EXAMPLES COMPLETE — no video rendered')