Add a garden Buddha and meditation tour chapter
This commit is contained in:
154
source/samford-retreat-garage/add_garden_buddha.py
Normal file
154
source/samford-retreat-garage/add_garden_buddha.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import bpy,math,os,json
|
||||
from mathutils import Vector,Matrix
|
||||
from math import sin,cos,pi
|
||||
O=os.path.dirname(__file__);s=bpy.context.scene
|
||||
c=bpy.data.collections.new('36 Buddha - meditation garden');bpy.data.collections['DETAIL - full geometry for renders'].children.link(c);COL={'new':c};active='new'
|
||||
src=open(O+'/../samford-modular/build_scene.py').read();exec(src[src.index('def mesh('):src.index('timber=textured(')])
|
||||
cubefs=[tuple(reversed(f)) for f in cubefs]
|
||||
stone=textured('Buddha - warm weathered limestone',(.31,.285,.235),(.49,.46,.38),(42,42,42),.83,.0018)
|
||||
shade=mat('Buddha - fine carved recess',(.18,.16,.125),.94)
|
||||
def ell(n,p,sz,ma=stone,rot=0,nu=40,nv=24):
|
||||
vs=[];fs=[]
|
||||
for j in range(nv+1):
|
||||
a=pi*j/nv
|
||||
for i in range(nu):
|
||||
b=2*pi*i/nu;x=sz[0]*sin(a)*cos(b);y=sz[1]*sin(a)*sin(b)
|
||||
vs.append((p[0]+x*cos(rot)-y*sin(rot),p[1]+x*sin(rot)+y*cos(rot),p[2]+sz[2]*cos(a)))
|
||||
for j in range(nv):
|
||||
for i in range(nu):k=j*nu+i;l=j*nu+(i+1)%nu;fs.append((k,l,l+nu,k+nu))
|
||||
ob=mesh('BUDDHA - '+n,vs,[tuple(reversed(f)) for f in fs],ma)
|
||||
for f in ob.data.polygons:f.use_smooth=True
|
||||
return ob
|
||||
def tube(n,points,r,ma=stone):
|
||||
# Catmull-Rom smooth carved lines / rounded limbs.
|
||||
pts=[];p=[Vector(q) for q in points]
|
||||
for i in range(len(p)-1):
|
||||
a=p[max(0,i-1)];b=p[i];cc=p[i+1];d=p[min(i+2,len(p)-1)]
|
||||
for j in range(8):
|
||||
t=j/8;pts.append(.5*(2*b+(-a+cc)*t+(2*a-5*b+4*cc-d)*t*t+(-a+3*b-3*cc+d)*t*t*t))
|
||||
pts.append(p[-1]);vs=[];fs=[]
|
||||
for i,v in enumerate(pts):
|
||||
q=(pts[min(i+1,len(pts)-1)]-pts[max(0,i-1)]).to_track_quat('Z','Y')
|
||||
rr=r*(.65+.35*sin(pi*i/(len(pts)-1)))
|
||||
for j in range(10):vs.append(tuple(v+q@Vector((rr*cos(j*2*pi/10),rr*sin(j*2*pi/10),0))))
|
||||
for i in range(len(pts)-1):
|
||||
for j in range(10):k=i*10+j;l=i*10+(j+1)%10;fs.append((k,l,l+10,k+10))
|
||||
fs.extend([tuple(reversed(range(10))),tuple(range(len(vs)-10,len(vs)))])
|
||||
ob=mesh('BUDDHA - '+n,vs,fs,ma)
|
||||
for f in ob.data.polygons:f.use_smooth=True
|
||||
return ob
|
||||
box('BUDDHA - low stone plinth',(0,0,.13),(1.5,1.12,.26),stone,.04)
|
||||
box('BUDDHA - plinth cap',(0,0,.285),(1.56,1.18,.075),stone,.025)
|
||||
ell('oval seat',(0,0,.37),(.65,.43,.075))
|
||||
ell('left folded robe knee',(-.30,-.075,.52),(.39,.32,.16),rot=-.22)
|
||||
ell('right folded robe knee',(.30,-.075,.52),(.39,.32,.16),rot=.22)
|
||||
ell('crossed shin left',(-.13,-.25,.555),(.40,.145,.13),rot=.23)
|
||||
ell('crossed shin right',(.15,-.22,.585),(.39,.14,.12),rot=-.24)
|
||||
ell('left resting foot',(.30,-.25,.655),(.19,.09,.075),rot=.25)
|
||||
ell('right resting foot',(-.29,-.28,.64),(.18,.09,.072),rot=-.20)
|
||||
# Sculpted torso profile narrows at waist and neck, rounded shoulders.
|
||||
vs=[];fs=[];profiles=[(.53,.26,.18),(.68,.235,.185),(.86,.24,.175),(1.05,.30,.16),(1.16,.30,.14),(1.23,.20,.12),(1.25,.105,.095)]
|
||||
for z,rx,ry in profiles:
|
||||
for i in range(48):a=i*2*pi/48;vs.append((rx*cos(a),.02+ry*sin(a),z))
|
||||
for j in range(len(profiles)-1):
|
||||
for i in range(48):k=j*48+i;l=j*48+(i+1)%48;fs.append((k,l,l+48,k+48))
|
||||
fs.extend([tuple(reversed(range(48))),tuple(range(len(vs)-48,len(vs)))])
|
||||
ob=mesh('BUDDHA - draped torso',vs,fs,stone)
|
||||
for f in ob.data.polygons:f.use_smooth=True
|
||||
mod=ob.modifiers.new('Soft carved robe','SUBSURF');mod.levels=2;mod.render_levels=2
|
||||
ell('neck',(0,.015,1.265),(.10,.10,.13))
|
||||
for sign in [-1,1]:
|
||||
ell('rounded robe shoulder',(sign*.265,0,1.135),(.095,.091,.107))
|
||||
tube('relaxed arm',[(sign*.265,0,1.15),(sign*.32,-.015,.99),(sign*.34,-.08,.80),(sign*.22,-.255,.725),(sign*.085,-.30,.715)],.075)
|
||||
ell('hand in meditation',(sign*.052,-.325,.73+sign*.012),(.13,.073,.037),rot=sign*.08)
|
||||
for i in range(3):tube('finger crease',[(sign*.015,-.375-i*.008,.734+sign*.012),(sign*.11,-.365-i*.008,.728+sign*.012)],.0014,shade)
|
||||
tube('touching thumbs',[(-.09,-.30,.755),(-.035,-.29,.79),(0,-.30,.785),(.035,-.29,.79),(.09,-.30,.755)],.014)
|
||||
# A serene face, lowered eyelids, elongated earlobes and ushnisha.
|
||||
ell('head',(0,0,1.48),(.157,.136,.21))
|
||||
ell('chin',(0,-.033,1.355),(.100,.088,.067))
|
||||
for sign in [-1,1]:
|
||||
ell('ear',(sign*.157,.002,1.46),(.037,.050,.104))
|
||||
ell('elongated earlobe',(sign*.158,-.002,1.365),(.026,.028,.055))
|
||||
tube('ear carving',[(sign*.168,-.036,1.52),(sign*.181,-.042,1.47),(sign*.171,-.037,1.40)],.004,shade)
|
||||
tube('gentle brow',[(sign*.022,-.127,1.527),(sign*.068,-.127,1.545),(sign*.113,-.105,1.53)],.009)
|
||||
tube('lowered eyelid',[(sign*.026,-.131,1.497),(sign*.061,-.133,1.486),(sign*.102,-.112,1.493)],.0035,shade)
|
||||
ell('nose bridge',(0,-.133,1.46),(.024,.029,.064))
|
||||
ell('nose tip',(0,-.155,1.423),(.029,.024,.017))
|
||||
tube('calm closed mouth',[(-.047,-.116,1.387),(-.022,-.130,1.392),(0,-.133,1.389),(.022,-.130,1.392),(.047,-.116,1.387)],.0028,shade)
|
||||
ell('lower lip',(0,-.123,1.378),(.037,.010,.009))
|
||||
ell('ushnisha topknot',(0,.022,1.723),(.087,.086,.092))
|
||||
for j in range(7):
|
||||
a=.16+j*.14;num=max(9,int(36*sin(a)))
|
||||
for i in range(num):
|
||||
b=2*pi*(i+.5*(j%2))/num
|
||||
ell('carved hair curl',(.161*sin(a)*cos(b),.143*sin(a)*sin(b),1.48+.214*cos(a)),(.018,.018,.015),nu=10,nv=6)
|
||||
for j in range(6):
|
||||
a=.20+j*.29;num=max(6,int(24*sin(a)))
|
||||
for i in range(num):
|
||||
b=2*pi*(i+.5*(j%2))/num
|
||||
ell('topknot carving',(.087*sin(a)*cos(b),.022+.086*sin(a)*sin(b),1.723+.092*cos(a)),(.013,.013,.011),nu=8,nv=6)
|
||||
# Robe seam and restrained shallow folds.
|
||||
tube('robe diagonal edge',[(-.18,-.095,1.20),(-.11,-.16,1.10),(.02,-.174,.97),(.15,-.15,.86),(.20,-.13,.77)],.013)
|
||||
for j in range(5):
|
||||
z=.61+j*.075;tube('robe fold',[(-.18,-.13,z+.04),(-.05,-.173,z),(.11,-.155,z-.008)],.0045)
|
||||
for sign in [-1,1]:
|
||||
for j in range(3):tube('knee robe fold',[(sign*.22,-.37,.57+j*.022),(sign*.42,-.30,.57+j*.025),(sign*.55,-.17,.55+j*.027)],.005)
|
||||
# Fuse the neck, shoulders and arms into one continuous stone carving.
|
||||
detail=bpy.data.collections['DETAIL - full geometry for renders'];was_hidden=detail.hide_viewport;detail.hide_viewport=False;bpy.context.view_layer.update()
|
||||
parts=[ob for ob in c.objects if any(k in ob.name for k in ['draped torso','rounded robe shoulder','relaxed arm',' - neck'])]
|
||||
vs=[];fs=[];dg=bpy.context.evaluated_depsgraph_get()
|
||||
for ob in parts:
|
||||
ev=ob.evaluated_get(dg);me=ev.to_mesh();off=len(vs);vs.extend(tuple(v.co) for v in me.vertices);fs.extend(tuple(off+i for i in p.vertices) for p in me.polygons);ev.to_mesh_clear()
|
||||
body=mesh('BUDDHA - continuous carved upper body',vs,fs,stone)
|
||||
bpy.context.view_layer.objects.active=body
|
||||
rem=body.modifiers.new('Joined limestone carving','REMESH');rem.mode='VOXEL';rem.voxel_size=.007;rem.use_smooth_shade=True
|
||||
bpy.ops.object.modifier_apply(modifier=rem.name)
|
||||
sm=body.modifiers.new('Stone carving finish','SMOOTH');sm.factor=.65;sm.iterations=5;bpy.ops.object.modifier_apply(modifier=sm.name)
|
||||
for ob in parts:bpy.data.objects.remove(ob,do_unlink=True)
|
||||
detail.hide_viewport=was_hidden
|
||||
# Rotate east to look toward shared court; north of meditation room, south of garden path.
|
||||
cx,cy=-12.4,15.35
|
||||
def ground(x,y):return -.024*y+.004*x+.12*sin(x*.19)*cos(y*.14)+.055*sin(x*.72+y*.32)
|
||||
zbase=max(ground(cx+x,cy+y) for x in [-.6,.6] for y in [-.8,.8])+.025
|
||||
T=Matrix.Translation((cx,cy,zbase))@Matrix.Rotation(pi/2,4,'Z')
|
||||
for ob in c.objects:ob.matrix_world=T@Matrix.LocRotScale(ob.location,ob.rotation_euler.to_quaternion(),ob.scale)
|
||||
# Single smooth lightweight viewport proxy, no object overhead from small carving details.
|
||||
vs=[];fs=[];inds=[];smooth=[];mats=[]
|
||||
for ob in c.objects:
|
||||
mw=Matrix.LocRotScale(ob.location,ob.rotation_euler.to_quaternion(),ob.scale);off=len(vs);vs.extend(tuple(mw@v.co) for v in ob.data.vertices)
|
||||
for m in ob.data.materials:
|
||||
if m not in mats:mats.append(m)
|
||||
for p in ob.data.polygons:fs.append(tuple(off+i for i in p.vertices));inds.append(mats.index(ob.data.materials[p.material_index]));smooth.append(p.use_smooth)
|
||||
me=bpy.data.meshes.new('Buddha garden viewport');me.from_pydata(vs,[],fs);me.update()
|
||||
for m in mats:me.materials.append(m)
|
||||
me.polygons.foreach_set('material_index',inds);me.polygons.foreach_set('use_smooth',smooth)
|
||||
proxy=bpy.data.objects.new('FAST | Garden Buddha statue',me);bpy.data.collections['00 FAST TOUR - lightweight viewport'].objects.link(proxy)
|
||||
chapters=json.load(open(O+'/tour-chapters-container-water.json'));title='31 / QUIET GARDEN BUDDHA';chapters.append(dict(title=title,start=3601,end=3720,seconds=5))
|
||||
cam=bpy.data.objects['PLAY TOUR camera'];s.frame_end=3720
|
||||
for f,p in [(3601,(-7.5,18.2,2.6)),(3720,(-7.8,17.7,2.35))]:
|
||||
s.frame_set(f);cam.location=p;cam.rotation_euler=(Vector((cx,cy,zbase+.95))-cam.location).to_track_quat('-Z','Y').to_euler();cam.keyframe_insert('location',frame=f);cam.keyframe_insert('rotation_euler',frame=f);cam.data.lens=49;cam.data.keyframe_insert('lens',frame=f)
|
||||
coll=bpy.data.collections['22 PLAY - guided camera tour'];fonts=[o for o in coll.objects if o.type=='FONT']
|
||||
ref=next(o for o in fonts if o.data.body.startswith('30 /'))
|
||||
ref.scale=(0,0,0);ref.keyframe_insert('scale',frame=3601)
|
||||
caption=ref.copy();caption.data=ref.data.copy();caption.animation_data_clear();coll.objects.link(caption);caption.name='Tour caption 31 - garden Buddha';caption.data.body=title;caption.location=(-36/49*.91,36/49*9/16*.84,-2)
|
||||
for f,k in [(1,0),(3600,0),(3601,1),(3720,1)]:caption.scale=(k,k,k);caption.keyframe_insert('scale',frame=f)
|
||||
for ob in [ref,caption]:
|
||||
for layer in ob.animation_data.action.layers:
|
||||
for strip in layer.strips:
|
||||
for bag in strip.channelbags:
|
||||
for fc in bag.fcurves:
|
||||
for key in fc.keyframe_points:key.interpolation='CONSTANT'
|
||||
s.timeline_markers.new(title,frame=3601);s['PLAY THE TOUR']='Hover over the 3D view and press Space. 31 chapters, 2 min 35 sec. Shift+Left Arrow restarts.'
|
||||
s['Garden Buddha']='Weathered limestone seated Buddha on a low stone plinth, north of the meditation pavilion, facing east into the shared garden court. Version 006.'
|
||||
text=bpy.data.texts.get('START HERE - PRESS PLAY')
|
||||
if text:text.clear();text.write(s['PLAY THE TOUR']+'\n\n'+s['Garden Buddha'])
|
||||
layout=json.load(open(O+'/container-water-layout.json'));layout.update(version=6,buddha=[cx,cy,zbase],buddha_facing='east')
|
||||
json.dump(layout,open(O+'/buddha-garden-layout.json','w'),indent=2);json.dump(chapters,open(O+'/tour-chapters-buddha-garden.json','w'),indent=2)
|
||||
s.frame_set(3601);s.render.resolution_percentage=100;s.camera=cam
|
||||
bpy.ops.wm.save_as_mainfile(filepath=O+'/samford-solarpunk-buddha-garden-tour.blend')
|
||||
for o in fonts+[caption]:o.hide_render=True
|
||||
s.render.resolution_percentage=60;s.cycles.samples=48;s.cycles.use_denoising=True
|
||||
prefs=bpy.context.preferences.addons['cycles'].preferences;prefs.compute_device_type='METAL';prefs.get_devices()
|
||||
for d in prefs.devices:d.use=d.type=='METAL'
|
||||
s.cycles.device='GPU';s.render.filepath=O+'/buddha-garden.png';bpy.ops.render.render(write_still=True)
|
||||
print('BUDDHA_READY',len(c.objects),len(fs),flush=True)
|
||||
Reference in New Issue
Block a user