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