Initiales Halloween-Projekt

This commit is contained in:
marcinator_2 committed 2026-10-01 03:19:17 +02:00
commit e071fbad2a
36 files changed
+2220

No files matched your search

+175
View File
@@ -0,0 +1,175 @@
import bpy, math, random, os, sys, wave, struct
from mathutils import Vector
random.seed(18)
ROOT=os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
OUT=os.path.join(ROOT,'preview')
bpy.ops.object.select_all(action='SELECT'); bpy.ops.object.delete(use_global=False)
s=bpy.context.scene
s.render.engine='BLENDER_EEVEE'; s.eevee.taa_render_samples=8; s.eevee.use_gtao=True; s.eevee.gtao_distance=3; s.eevee.use_soft_shadows=True
s.render.resolution_x=1280;s.render.resolution_y=720;s.render.resolution_percentage=100
s.render.fps=24;s.frame_start=1;s.frame_end=480
s.world.color=(.003,.003,.003)
s.view_settings.view_transform='AgX'
def mat(name,color,rough=.7,noise=False):
m=bpy.data.materials.new(name);m.diffuse_color=(*color,1);m.use_nodes=True
p=m.node_tree.nodes.get('Principled BSDF');p.inputs['Base Color'].default_value=(*color,1);p.inputs['Roughness'].default_value=rough
if noise:
n=m.node_tree.nodes.new('ShaderNodeTexNoise');n.inputs['Scale'].default_value=24
b=m.node_tree.nodes.new('ShaderNodeBump');b.inputs['Strength'].default_value=.3;b.inputs['Distance'].default_value=.08
m.node_tree.links.new(n.outputs['Fac'],b.inputs['Height']);m.node_tree.links.new(b.outputs['Normal'],p.inputs['Normal'])
return m
soil=mat('Damp garden soil',(.07,.045,.025),noise=True);stone=mat('Weathered limestone',(.28,.29,.27),noise=True)
cloth=mat('Burial cloth',(.15,.12,.09),noise=True);brick=mat('Rendered house wall',(.24,.19,.14),noise=True)
bone=mat('Old ivory',(.55,.48,.32));black=mat('Dark fabric',(.015,.02,.025),noise=True)
leafm=[mat('Autumn leaf '+str(i),c) for i,c in enumerate([(.21,.065,.014),(.30,.15,.03),(.12,.055,.018)])]
def cube(name,loc,scale,m,bevel=0):
bpy.ops.mesh.primitive_cube_add(size=1,location=loc);o=bpy.context.object;o.name=name;o.scale=scale
bpy.ops.object.transform_apply(location=False,rotation=False,scale=True)
if m:o.data.materials.append(m)
if bevel:
mod=o.modifiers.new('Soft edges','BEVEL');mod.width=bevel;mod.segments=3;o.data.use_auto_smooth=True;o.modifiers.new('Normals','WEIGHTED_NORMAL')
return o
def uv(name,loc,scale,m):
bpy.ops.mesh.primitive_uv_sphere_add(segments=16,ring_count=10,location=loc);o=bpy.context.object;o.name=name;o.scale=scale;o.data.materials.append(m)
for p in o.data.polygons:p.use_smooth=True
return o
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
cube('Ground',(0,0,-.12),(16,16,.2),soil)
cube('Walkway',(2.6,0,.01),(1.5,12,.09),stone,.03)
for y in range(-6,7):cube('Paving joint',(2.6,y,.065),(1.5,.017,.005),black)
cube('House',(0,6,2),(12,.3,4),brick)
for x in [-3,0,3]:
cube('Window frame',(x,5.80,2.2),(1.6,.15,1.8),black,.04)
glass=mat('Warm window '+str(x),(.5,.22,.045));p=glass.node_tree.nodes.get('Principled BSDF');p.inputs['Emission Color'].default_value=(1,.35,.08,1);p.inputs['Emission Strength'].default_value=.8
cube('Lit window',(x,5.70,2.2),(1.4,.04,1.6),glass)
cube('Window divider',(x,5.65,2.2),(.05,.06,1.7),black)
for x in [-5,5]:
for y in range(-4,6):cube('Fence post',(x,y,.65),(.09,.09,1.3),stone,.015)
cube('Fence rail',(x,.5,.9),(.07,10,.08),stone)
# Grave border and headstone
for x in [-.9,.9]:cube('Grave side',(x,0,.14),(.15,2.9,.28),stone,.04)
for y in [-1.45,1.45]:cube('Grave end',(0,y,.14),(1.95,.15,.28),stone,.04)
cube('Headstone base',(0,1.65,.25),(1.1,.45,.3),stone,.06)
cube('Headstone',(0,1.65,.85),(.85,.22,1.1),stone,.10)
uv('Rounded headstone',(0,1.65,1.35),(.425,.11,.3),stone)
def text(body,loc,size):
bpy.ops.object.text_add(location=loc,rotation=(math.pi/2,0,0));o=bpy.context.object;o.data.body=body;o.data.align_x='CENTER';o.data.size=size;o.data.extrude=.002;o.data.materials.append(black)
text('R I P',(0,1.525,1.05),.19);text('STILL BREATHING',(0,1.525,.72),.065)
# Fabric mesh with animated breathing shape
verts=[];faces=[];nx=28;ny=44
for j in range(ny+1):
y=-1.35+2.7*j/ny
for i in range(nx+1):
x=-.80+1.6*i/nx;z=.19+.035*math.sin(i*1.3+j*.7)*math.sin(math.pi*i/nx)*math.sin(math.pi*j/ny);verts.append((x,y,z))
for j in range(ny):
for i in range(nx):
a=j*(nx+1)+i;faces.append((a,a+1,a+nx+2,a+nx+1))
mesh=bpy.data.meshes.new('Cloth grid');mesh.from_pydata(verts,[],faces);mesh.update()
o=bpy.data.objects.new('Breathing burial shroud',mesh);s.collection.objects.link(o);o.data.materials.append(cloth)
for p in mesh.polygons:p.use_smooth=True
o.shape_key_add(name='Rest');breath=o.shape_key_add(name='Breath')
for v in breath.data:v.co.z+=.23*math.exp(-((v.co.x/.50)**2+(v.co.y/.85)**2))
for f,val in [(1,.1),(30,1),(65,0),(100,.85),(120,.15),(150,0),(175,.45),(210,0),(240,.1),(241,0),(310,0),(316,1.8),(322,.1),(332,1.2),(345,0),(380,.1),(415,.8),(450,0),(480,.35)]:breath.value=val;breath.keyframe_insert('value',frame=f)
for i in range(220):
x=random.uniform(-4,4);y=random.uniform(-4,4)
if abs(x)<1 and abs(y)<1.5:continue
me=bpy.data.meshes.new('Leaf');me.from_pydata([(-.10,0,0),(-.045,-.04,0),(0,-.065,.01),(.07,-.025,0),(.11,0,0),(.04,.045,0),(-.02,.055,.008)],[],[(0,1,2,3,4,5,6)]);me.update()
l=bpy.data.objects.new('Fallen leaf',me);s.collection.objects.link(l);l.location=(x,y,.025);l.data.materials.append(random.choice(leafm));l.rotation_euler.z=random.random()*6.28
# Grass tufts, shrubs, and Halloween pumpkins
grassmat=mat('Dry grass',(.055,.065,.024))
for i in range(180):
x=random.uniform(-4.5,1.6);y=random.uniform(-4,4)
if abs(x)<1.1 and abs(y)<1.8:continue
rod('Grass blade',(x,y,0),(x+.025,y+.015,random.uniform(.07,.17)),.003,grassmat)
for x,y in [(-2,1),(-3,3),(4,4)]:
for j in range(8):uv('Garden shrub',(x+random.uniform(-.3,.3),y+random.uniform(-.3,.3),random.uniform(.2,.5)),(.3,.3,.3),grassmat)
for x,y in [(-1.5,-1.7),(-1.8,-1.4),(.8,2.1)]:
pumpkin=uv('Ribbed pumpkin',(x,y,.18),(.23,.23,.18),leafm[0])
for v in pumpkin.data.vertices:
ang=math.atan2(v.co.y,v.co.x);fac=1+.055*math.cos(ang*10);v.co.x*=fac;v.co.y*=fac
rod('Pumpkin stem',(x,y,.32),(x+.02,y,.42),.024,soil)
# Cloaked costumed visitors, seen from behind/side
purple=mat('Purple wool costume',(.11,.025,.13),noise=True);red=mat('Burgundy costume',(.18,.018,.022),noise=True)
def person(name,h,color,start,end,child=False):
root=bpy.data.objects.new(name,None);s.collection.objects.link(root)
# Folded tapered cloak
vs=[];fs=[];rings=14;seg=32
for j in range(rings):
z=h*(.18+.60*j/(rings-1));r=h*(.24-.12*j/(rings-1))
for k in range(seg):
ang=k*2*math.pi/seg;rr=r*(1+.085*math.sin(ang*9+j*.25));vs.append((rr*math.cos(ang),rr*.65*math.sin(ang),z))
for j in range(rings-1):
for k in range(seg):a=j*seg+k;b=j*seg+(k+1)%seg;fs.append((a,b,b+seg,a+seg))
me=bpy.data.meshes.new(name+' costume');me.from_pydata(vs,[],fs);me.update();ob=bpy.data.objects.new(name+' cloak',me);s.collection.objects.link(ob);ob.parent=root;ob.data.materials.append(color)
for p in me.polygons:p.use_smooth=True
for x in [-h*.08,h*.08]:
ob=uv('Boot',(x,-.04,h*.08),(h*.065,h*.10,h*.09),black);ob.parent=root
drv=ob.driver_add('location',2).driver;drv.expression=f'{h*.08} + (max(0,sin(frame*0.32+{0 if x<0 else math.pi}))*{h*.035} if 120<frame<165 else 0)'
ob=uv('Hood',(0,0,h*.86),(h*.12,h*.12,h*.15),color);ob.parent=root
# dark face opening directed toward grave (-X)
ob=uv('Hood shadow',(-h*.10,0,h*.85),(h*.025,h*.075,h*.09),black);ob.parent=root
for sign in [-1,1]:
ob=uv('Sleeve',(0,sign*h*.14,h*.56),(h*.07,h*.07,h*.24),color);ob.rotation_euler.x=sign*.12;ob.parent=root
if child:
ob=uv('Pumpkin candy bucket',(-h*.12,-h*.27,h*.35),(h*.09,h*.09,h*.08),leafm[1]);ob.parent=root
for f,pos in start:root.location=pos;root.keyframe_insert('location',frame=f)
for f,angle in end:root.rotation_euler.z=angle;root.keyframe_insert('rotation_euler',frame=f)
return root
person('Adult A',1.78,black,[(1,(3.2,-10,0)),(120,(3.2,-3,0)),(165,(2.7,.0,0)),(235,(2.7,1.5,0)),(240,(3,7,0)),(241,(2.7,-.35,0)),(310,(2.7,-.35,0)),(325,(3.05,-.75,0)),(390,(3.05,-.75,0)),(480,(3.2,.2,0))],[(1,0),(310,0),(325,-.15),(380,0)])
person('Adult B',1.68,red,[(1,(3.6,-10.8,0)),(120,(3.6,-3.6,0)),(165,(3.35,-.5,0)),(235,(3.35,1.0,0)),(240,(3,7,0)),(241,(3.45,.5,0)),(310,(3.45,.5,0)),(325,(3.75,.9,0)),(480,(3.65,1.3,0))],[(1,0),(310,0),(325,.15),(380,0)])
for name,h,x,y,c in [('Child witch',1.05,2.35,-.8,purple),('Child vampire',1.2,3.1,-1.35,black)]:
person(name,h,c,[(1,(x,-11,0)),(120,(x,-3.8,0)),(165,(x,y,0)),(220,(x,y+.8,0)),(239,(x,3.5,0)),(240,(x,8,0)),(480,(x,8,0))],[(1,0)],True)
# Emerging skeletal hand
hand=bpy.data.objects.new('Emerging hand',None);s.collection.objects.link(hand);hand.location=(.65,-1.15,-.4)
ob=uv('Palm',(0,0,.4),(.09,.045,.12),bone);ob.parent=hand
rod('Forearm',(0,0,0),(0,0,.32),.045,bone,hand)
for i in range(4):
x=(i-1.5)*.045;top=.64+(.025 if i in [1,2] else 0)
rod('Finger',(x,0,.48),(x,-.025,top),.015,bone,hand);rod('Finger tip',(x,-.025,top),(x,-.065,top+.035),.012,bone,hand)
rod('Thumb',(-.075,0,.4),(-.14,-.04,.48),.02,bone,hand)
for f,z in [(1,-.65),(310,-.65),(320,.03),(345,.05),(365,-.65),(480,-.65)]:hand.location.z=z;hand.keyframe_insert('location',frame=f)
def light(name,loc,color,power,size=3):
data=bpy.data.lights.new(name,'AREA');data.energy=power;data.color=color;data.shape='DISK';data.size=size
ob=bpy.data.objects.new(name,data);s.collection.objects.link(ob);ob.location=loc;ob.rotation_euler=(Vector((0,0,.3))-ob.location).to_track_quat('-Z','Y').to_euler();return data
light('Moon',(0,-2,7),(.27,.46,1),450,7)
light('Porch',(1,4,3),(1,.46,.16),240,4)
light('Grave blue',(-1,1,1.9),(.025,.12,1),110,2)
flash=light('Brief reveal',(1,-2,2),(1,.72,.38),0,1)
for f,e in [(1,0),(312,0),(316,600),(321,40),(330,260),(337,0),(480,0)]:flash.energy=e;flash.keyframe_insert('energy',frame=f)
# Cinematic camera
bpy.ops.object.camera_add();cam=bpy.context.object;s.camera=cam;cam.data.lens=38
for f,loc,target in [(1,(7,-9,4),(0,0,.55)),(120,(4.8,-6.3,3.1),(0,0,.55)),(240,(4.8,-6.3,3.1),(.7,0,.65)),(241,(6.5,-7,3.3),(.6,0,.65)),(380,(6.5,-7,3.3),(.6,0,.65)),(480,(7,-8.5,4.5),(0,.5,.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)
# Make cuts and visitor reset instantaneous rather than interpolated
for ob in [cam]+[o for o in s.objects if o.type=='EMPTY' and o.name.startswith(('Adult','Child'))]:
if ob.animation_data:
for fc in ob.animation_data.action.fcurves:
for k in fc.keyframe_points:
if int(k.co.x) in [240,239]:k.interpolation='CONSTANT'
# Sound: filtered noise breath, scratching and synthetic growl
rate=24000;random.seed(72);samples=[];lp=0
for i in range(rate*20):
t=i/rate;lp=.90*lp+.10*random.uniform(-1,1)
env=(.5+.5*math.sin(t*2.1))**3
a=.07*lp*env if t<10 or t>17 else .006*lp
for begin,end in [(7.1,8.3),(13,13.45)]:
if begin<t<end:a+=.13*lp*(.5+.5*math.sin(t*90))
if 13.15<t<16.1:
e=math.exp(-(t-13.15)*1.25);a+=e*(.27*math.sin(2*math.pi*62*t+3*math.sin(t*17))+.25*lp)
samples.append(int(max(-.9,min(.9,a))*32767))
audio=os.path.join(OUT,'effects.wav')
with wave.open(audio,'wb') as w:w.setparams((1,2,rate,len(samples),'NONE','not compressed'));w.writeframes(struct.pack('<%dh'%len(samples),*samples))
s.sequence_editor_create();s.sequence_editor.sequences.new_sound('Generated ambience and effects',audio,channel=1,frame_start=1)
s.render.image_settings.file_format='PNG';s.render.filepath=os.path.join(OUT,'frames','frame_')
bpy.ops.file.pack_all()
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'halloween_preview.blend'))
mode=sys.argv[-1]
if mode=='previews':
for f in [80,185,320]:
s.frame_set(f);s.render.filepath=os.path.join(OUT,'preview_%03d.png'%f);bpy.ops.render.render(write_still=True)
elif mode=='video':
s.render.image_settings.file_format='FFMPEG';s.render.ffmpeg.format='MPEG4';s.render.ffmpeg.codec='H264';s.render.ffmpeg.constant_rate_factor='MEDIUM';s.render.ffmpeg.audio_codec='AAC';s.render.ffmpeg.audio_bitrate=192;s.render.filepath=os.path.join(OUT,'halloween_preview.mp4');bpy.ops.render.render(animation=True)