Preserve homestead context and garden-centred Blender design
This commit is contained in:
269
source/samford-retreat-garage/revise_scene.py
Normal file
269
source/samford-retreat-garage/revise_scene.py
Normal file
@@ -0,0 +1,269 @@
|
||||
import bpy, math, os, json, random, bmesh
|
||||
from mathutils import Vector
|
||||
from math import sin,cos,pi
|
||||
random.seed(602)
|
||||
OUT=os.path.dirname(os.path.abspath(__file__)); s=bpy.context.scene
|
||||
def center(o): return o.matrix_world@(sum((Vector(v) for v in o.bound_box),Vector())/8)
|
||||
def ground(x,y): return -.024*y+.004*x+.12*sin(x*.19)*cos(y*.14)+.055*sin(x*.72+y*.32)
|
||||
# Keep one room as a retreat; the other two become undeveloped shadow footprints.
|
||||
med=bpy.data.collections['23 FUTURE - Child 1 private room']; med.name='23 NOW - Quiet meditation room'; med['Purpose']='Simple meditation retreat now; adaptable room in future.'
|
||||
for name in ['24 FUTURE - Child 2 private room','25 FUTURE - Child 3 private room','26 Children courtyard and covered links']:
|
||||
c=bpy.data.collections.get(name)
|
||||
if c:
|
||||
for o in list(c.objects): bpy.data.objects.remove(o,do_unlink=True)
|
||||
bpy.data.collections.remove(c)
|
||||
for o in list(med.objects):
|
||||
if any(t in o.name for t in ['single bed','mattress','blanket','pillow','desk','stool','wardrobe','pinboard','display shelf']): bpy.data.objects.remove(o,do_unlink=True)
|
||||
elif o.type=='FONT': o.data.body='QUIET / MEDITATION'; o.name='Meditation room entry sign'
|
||||
else: o.name=o.name.replace('Child 1','Meditation').replace('Child room','Meditation room')
|
||||
# Remove roof-junction leftovers from the two deleted rooms, which were stored separately.
|
||||
for o in list(bpy.data.collections['20 Spacious site connections'].objects):
|
||||
if o.name.startswith('Child room'):
|
||||
if center(o).x>-6: bpy.data.objects.remove(o,do_unlink=True)
|
||||
else: o.name=o.name.replace('Child room','Meditation room')
|
||||
COL={'med':med}
|
||||
for key,name in [('future','24 LATER - Shadow spaces only'),('road','27 Gravel access road and arrival'),('garage','28 Garage and workbench'),('car','29 Car and bicycles'),('cutaway','30 VIEW - Transparent building sides')]:
|
||||
c=bpy.data.collections.new(name); s.collection.children.link(c); COL[key]=c
|
||||
active='med'; src=open(os.path.join(OUT,'../samford-modular/build_scene.py')).read(); exec(src[src.index('def mesh('):src.index('timber=textured(')])
|
||||
wood=bpy.data.materials['Unpainted hardwood - long X grain']; woodz=bpy.data.materials['Oiled spotted gum posts - vertical grain']; steel=bpy.data.materials['Reusable dark steel bolts and shoes']; stone=bpy.data.materials['Split sandstone facing - mineral grain']; roofmat=bpy.data.materials['Pale Colorbond - powder coated steel']; clay=bpy.data.materials['Soft warm clay plaster - natural mineral texture']; glass=bpy.data.materials['Clear glazing']; weather=bpy.data.materials['Naturally silvered hardwood - horizontal X']; linen=bpy.data.materials['Undyed flax linen']; gravel=bpy.data.materials['Loose local gravel - fine stones']; F=1.12
|
||||
zafu=textured('Meditation cushion charcoal linen',(.085,.08,.063),(.19,.17,.13),(100,100,100),.95,.003)
|
||||
tatami=textured('Woven natural meditation mat',(.33,.28,.17),(.54,.47,.31),(120,8,120),.94,.004)
|
||||
wax=mat('Beeswax candle',(.70,.50,.22),.63); flame=mat('Small warm candle flame',(.95,.43,.08),.5); p=flame.node_tree.nodes['Principled BSDF']; p.inputs['Emission Color'].default_value=(1,.34,.04,1); p.inputs['Emission Strength'].default_value=5
|
||||
box('Simple woven tatami mat',(-11,12,F+.025),(2.05,2.2,.05),tatami,.035)
|
||||
box('Meditation square zabuton',(-11,12.05,F+.075),(.95,.95,.10),linen,.10)
|
||||
rod('ROUND MEDITATION CUSHION',(-11,12.05,F+.12),(-11,12.05,F+.28),.34,zafu,sides=64)
|
||||
box('Low stone candle shelf',(-11,10.36,F+.29),(1.35,.38,.12),stone,.022)
|
||||
for x,h in [(-11.42,.16),(-11,.22),(-10.58,.13)]:
|
||||
rod('Stone candle holder',(x,10.36,F+.35),(x,10.36,F+.39),.10,stone,sides=24)
|
||||
rod('Meditation candle',(x,10.36,F+.39),(x,10.36,F+.39+h),.041,wax,sides=24)
|
||||
rod('Meditation candle wick',(x,10.36,F+.39+h),(x,10.36,F+.415+h),.003,steel,sides=8)
|
||||
rod('Small candle flame',(x,10.36,F+.40+h),(x,10.36,F+.46+h),.014,flame,r2=.001,sides=12)
|
||||
d=bpy.data.lights.new('Soft candle glow','POINT'); d.energy=1.5; d.color=(1,.39,.10); d.shadow_soft_size=.06; ob=bpy.data.objects.new(d.name,d); COL[active].objects.link(ob); ob.location=(x,10.36,F+.44+h)
|
||||
box('Meditation low timber bench',(-12.6,11.9,F+.37),(.45,1.45,.065),wood,.02)
|
||||
for y in [11.34,12.46]: box('Meditation bench leg',(-12.6,y,F+.18),(.36,.05,.36),wood)
|
||||
# A minimal shaded walk goes only to the actual meditation room.
|
||||
for cx,cy,w,dep in [(-3,7.2,1.2,3.6),(-7,9,8,1.2)]:
|
||||
box('Meditation covered walk deck',(cx,cy,F-.07),(w,dep,.14),wood,.008)
|
||||
box('Meditation covered walk roof',(cx,cy,F+2.55),(w+.14,dep+.14,.055),roofmat,.008)
|
||||
for x,y in [(cx-w/2+.1,cy-dep/2+.1),(cx+w/2-.1,cy+dep/2-.1)]:
|
||||
g=ground(x,y); box('Meditation walk timber post',(x,y,(g+F+2.52)/2),(.085,.085,F+2.52-g),woodz,.006)
|
||||
def ghost(name,color,opacity):
|
||||
m=mat(name,color,.8); ns=m.node_tree.nodes; ls=m.node_tree.links; p=ns.get('Principled BSDF'); out=ns.get('Material Output'); tr=ns.new('ShaderNodeBsdfTransparent'); mix=ns.new('ShaderNodeMixShader'); mix.inputs[0].default_value=opacity; ls.new(tr.outputs[0],mix.inputs[1]); ls.new(p.outputs[0],mix.inputs[2]); ls.new(mix.outputs[0],out.inputs['Surface']); m.diffuse_color=(*color,opacity); return m
|
||||
active='future'; gm=ghost('Future shadow - faint sage outline',(.49,.59,.50),.32); shade=ghost('Future shadow - translucent footprint',(.20,.26,.21),.10)
|
||||
for idx,(cx,cy) in enumerate([(-3,17),(7,15)]):
|
||||
xs,xe=cx-2.25,cx+2.25; ys,yn=cy-2.4,cy+2.4
|
||||
for x in [xs,xe]:
|
||||
for y,h in [(ys,2.75),(yn,3.1)]: beam('Future space %d | shadow edge'%(idx+1),(x,y,F),(x,y,F+h),.028,.028,gm)
|
||||
beam('Future shadow roof slope',(x,ys,F+2.75),(x,yn,F+3.1),.028,.028,gm)
|
||||
for y,h in [(ys,2.75),(yn,3.1)]:
|
||||
beam('Future shadow roof line',(xs,y,F+h),(xe,y,F+h),.028,.028,gm)
|
||||
beam('Future shadow floor line',(xs,y,F),(xe,y,F),.028,.028,gm)
|
||||
for x in [xs,xe]: beam('Future shadow floor side',(x,ys,F),(x,yn,F),.028,.028,gm)
|
||||
o=box('Future space %d | ground shadow'%(idx+1),(cx,cy,ground(cx,cy)+.03),(4.5,4.8,.01),shade); o['Status']='Unbuilt reserve: no furniture, solid walls or occupied room.'
|
||||
# Road enters from the southeast and approaches a turning apron in front of the garage.
|
||||
active='road'; roadpoints=[(36,-40),(28,-33),(22,-27),(8,-26),(5,-22),(5,-13),(1,-12),(-3,-12)]
|
||||
def road(name,points,width):
|
||||
vs=[]; fs=[]
|
||||
for aa,bb in zip(points,points[1:]):
|
||||
a,b=Vector(aa),Vector(bb); delta=b-a; perp=Vector((-delta.y,delta.x)).normalized()*width/2; n=max(3,int(delta.length/.45))
|
||||
start=len(vs)
|
||||
for i in range(n):
|
||||
p=a+delta*i/(n-1)
|
||||
for q in [p-perp,p+perp]: vs.append((q.x,q.y,ground(q.x,q.y)+.065))
|
||||
for i in range(n-1): k=start+i*2; fs.append((k,k+1,k+3,k+2))
|
||||
return mesh(name,vs,fs,gravel)
|
||||
road('VEHICLE ACCESS - 4m gravel driveway',roadpoints,4)
|
||||
# Gravel apron draped over local terrain.
|
||||
vs=[]; fs=[]
|
||||
for j in range(9):
|
||||
for i in range(17):
|
||||
x=-7+i*.75; y=-15.5+j*.75; vs.append((x,y,ground(x,y)+.07))
|
||||
for j in range(8):
|
||||
for i in range(16): k=j*17+i; fs.append((k,k+1,k+18,k+17))
|
||||
mesh('Gravel turning and garage arrival apron',vs,fs,gravel)
|
||||
road('Garage to house pedestrian path',[(-3,-10),(-2,-6),(-5.5,-4),(-6.5,-2.8)],1.1)
|
||||
for x,y in [(34,-37),(29,-32),(22,-27),(6,-24),(5,-18),(3,-10)]:
|
||||
g=ground(x,y); box('Simple timber driveway edge marker',(x+2.35,y,g+.37),(.09,.09,.74),woodz,.01)
|
||||
# Detached garage: one car bay, workshop bench and a clear cycle-storage zone.
|
||||
active='garage'; gx,gy=-3,-19; GF=.62; w,dep=8,7; xs,xe=gx-4,gx+4; ys,yn=gy-3.5,gy+3.5
|
||||
slab=box('GARAGE - 8m x 7m concrete floor',(gx,gy,GF-.11),(w,dep,.22),bpy.data.materials['Warm oxide polished concrete - fine aggregate'],.018)
|
||||
for x in [xs+.15,xe-.15]:
|
||||
for y in [ys+.15,yn-.15]:
|
||||
g=ground(x,y); box('Garage concrete foundation pier',(x,y,(g+GF-.2)/2),(.40,.40,max(.08,GF-.2-g)),stone,.015)
|
||||
box('Garage back clay wall',(gx,ys,GF+1.44),(w,.17,2.88),clay)
|
||||
box('Garage west weatherboard wall',(xs,gy,GF+1.51),(.17,dep,3.02),weather)
|
||||
box('Garage east workshop wall',(xe,gy,GF+1.51),(.17,dep,3.02),clay)
|
||||
# The north viewing facade retains a closed-door design underneath the transparent presentation.
|
||||
box('Garage north front panel',(-5.0,yn,GF+1.43),(3.7,.15,2.86),weather)
|
||||
box('Garage north pedestrian door',(.24,yn,GF+1.16),(1.16,.07,2.32),wood,.009)
|
||||
box('Garage north narrow infill',(-.66,yn,GF+1.43),(.61,.15,2.86),weather)
|
||||
box('Garage north upper header',(gx,yn,GF+3.03),(w,.17,.36),wood)
|
||||
box('Garage roller door concept',(-2.14,yn,GF+1.30),(1.9,.09,2.6),roofmat,.008)
|
||||
# The car bay uses the broad west section: transparent front panels expose its parked car.
|
||||
for x in [xs,xe,-2.8]:
|
||||
for y,h in [(ys,2.95),(yn,3.3)]: box('Garage exposed timber post',(x,y,GF+h/2),(.14,.14,h),woodz,.005)
|
||||
for i in range(12):
|
||||
x=xs+i*w/11; beam('Garage exposed rafter',(x,ys-.30,GF+2.94),(x,yn+.30,GF+3.33),.07,.20,wood)
|
||||
ro=box('Garage pale mono-pitch roof',(gx,gy,GF+3.22),(8.7,7.7,.075),roofmat,.01); ro.rotation_euler.x=math.atan(.35/7)
|
||||
box('Garage rear roof gutter',(gx,ys-.40,GF+3.03),(8.7,.14,.10),roofmat,.006)
|
||||
# Entrance ramp blends the vehicle bay floor down to the apron.
|
||||
vs=[(x,y,z) for x in [xs+.2,gx+.3] for y,z in [(yn,GF),(yn+2.7,ground(x,yn+2.7)+.07)]]
|
||||
mesh('Garage shallow entry ramp',vs,[(0,2,3,1)],gravel)
|
||||
benchx,benchy=-.47,-21.5
|
||||
box('GARAGE WORKSTATION - timber workbench',(benchx,benchy,GF+.92),(2.50,.76,.09),wood,.015)
|
||||
for x in [benchx-1.07,benchx+1.07]:
|
||||
for y in [benchy-.28,benchy+.28]: box('Workbench steel leg',(x,y,GF+.44),(.055,.055,.88),steel,.005)
|
||||
box('Workbench lower shelf',(benchx,benchy,GF+.22),(2.4,.66,.045),wood)
|
||||
box('Workshop toolboard',(benchx,ys+.12,GF+1.85),(2.5,.07,.98),wood,.008)
|
||||
for i in range(8):
|
||||
x=benchx-1.05+i*.30; rod('Workbench tool hook',(x,ys+.17,GF+1.9),(x,ys+.25,GF+1.9),.009,steel)
|
||||
box('Workshop hanging tool handle',(x,ys+.23,GF+1.68),(.03,.03,.27),woodz,.005)
|
||||
box('Workshop tool head',(x,ys+.23,GF+1.89),(.10,.045,.06),steel,.008)
|
||||
box('Workbench small vice',(benchx-.85,benchy+.34,GF+1.04),(.25,.20,.17),steel,.02)
|
||||
rod('Vice turning handle',(benchx-1.00,benchy+.48,GF+1.03),(benchx-.69,benchy+.48,GF+1.03),.012,steel)
|
||||
black=mat('Garage rubber and dark tools',(.025,.03,.028),.78); alu=mat('Garage brushed aluminium',(.40,.43,.43),.30,.8)
|
||||
box('Workbench compact display',(benchx+.5,benchy-.19,GF+1.22),(.56,.04,.34),black,.015)
|
||||
box('Workbench keyboard',(benchx+.5,benchy+.12,GF+.98),(.34,.12,.018),black,.007)
|
||||
box('Workbench stool',(benchx,benchy+1.1,GF+.51),(.42,.42,.06),wood,.025)
|
||||
for x in [benchx-.16,benchx+.16]:
|
||||
for y in [benchy+.94,benchy+1.26]: box('Workbench stool leg',(x,y,GF+.24),(.035,.035,.48),steel)
|
||||
light=bpy.data.lights.new('Garage warm work light','AREA'); light.energy=320; light.size=3.4; light.color=(1,.85,.66); ob=bpy.data.objects.new(light.name,light); COL[active].objects.link(ob); ob.location=(-2,-19,GF+2.7)
|
||||
# One generic family wagon, parked nose toward the transparent front.
|
||||
active='car'; paint=mat('Family wagon muted green',(.20,.28,.25),.32,.4); autoglass=mat('Vehicle tinted glass',(.035,.07,.085),.20,.3); lamps=mat('Vehicle headlamp lens',(.70,.76,.73),.22)
|
||||
cx,cy=-4.7,-19.1
|
||||
box('ONE FAMILY CAR - wagon body',(cx,cy,GF+.70),(1.86,4.35,.56),paint,.16)
|
||||
box('Car lower sill',(cx,cy,GF+.46),(1.77,4.12,.22),black,.08)
|
||||
vs=[(cx+x,cy+y,GF+z) for x,y,z in [(-.81,-1.25,.96),(.81,-1.25,.96),(.81,1.08,.96),(-.81,1.08,.96),(-.69,-.97,1.65),(.69,-.97,1.65),(.69,.64,1.65),(-.69,.64,1.65)]]
|
||||
mesh('Family wagon glazed cabin',vs,[(0,1,5,4),(1,2,6,5),(2,3,7,6),(3,0,4,7),(4,5,6,7)],autoglass)
|
||||
box('Car roof',(cx,cy-.16,GF+1.67),(1.43,1.65,.075),paint,.05)
|
||||
for x in [cx-.87,cx+.87]:
|
||||
for y in [cy-1.40,cy+1.40]:
|
||||
rod('Car rubber tyre',(x-.11,y,GF+.36),(x+.11,y,GF+.36),.35,black,sides=48)
|
||||
rod('Car alloy wheel',(x-.12,y,GF+.36),(x+.12,y,GF+.36),.23,alu,sides=32)
|
||||
box('Car side mirror',(x+(.13 if x>cx else -.13),cy+.55,GF+1.08),(.20,.24,.12),paint,.05)
|
||||
box('Car door handle',(x+(.07 if x>cx else -.07),cy,GF+.93),(.025,.17,.027),alu,.01)
|
||||
for x in [cx-.61,cx+.61]: box('Car front headlamp',(x,cy+2.14,GF+.82),(.45,.04,.15),lamps,.045)
|
||||
box('Car front bumper',(cx,cy+2.18,GF+.50),(1.68,.05,.15),black,.05)
|
||||
box('Car blank number plate',(cx,cy+2.22,GF+.65),(.42,.018,.10),lamps,.01)
|
||||
# Two bicycles in a three-position floor rack beside the car, clear of the bench.
|
||||
bikemat=[mat('Bicycle ochre frame',(.53,.31,.10),.4,.45),mat('Bicycle blue green frame',(.10,.30,.31),.4,.45)]
|
||||
def hoop(name,x,y,z,r,tube,material):
|
||||
n=56; vs=[]; fs=[]
|
||||
for i in range(n):
|
||||
a=2*pi*i/n
|
||||
for j in range(8):
|
||||
b=2*pi*j/8; rr=r+tube*cos(b); vs.append((x+tube*sin(b),y+rr*cos(a),z+rr*sin(a)))
|
||||
for i in range(n):
|
||||
for j in range(8): fs.append((i*8+j,i*8+(j+1)%8,((i+1)%n)*8+(j+1)%8,((i+1)%n)*8+j))
|
||||
o=mesh(name,vs,fs,material)
|
||||
for p in o.data.polygons:p.use_smooth=True
|
||||
for idx,bx in enumerate([-.65,.25]):
|
||||
by=-18.0; z=GF+.36; m=bikemat[idx]; rear=Vector((bx,by-.66,z)); front=Vector((bx,by+.66,z)); crank=Vector((bx,by-.04,z+.05)); saddle=Vector((bx,by-.27,z+.63)); head=Vector((bx,by+.43,z+.59))
|
||||
for wheel in [rear,front]:
|
||||
hoop('Bicycle %d tyre'%(idx+1),wheel.x,wheel.y,wheel.z,.34,.022,black)
|
||||
hoop('Bicycle aluminium rim',wheel.x,wheel.y,wheel.z,.302,.010,alu)
|
||||
for i in range(12):
|
||||
a=2*pi*i/12; rod('Bicycle spoke',wheel,wheel+Vector((0,.302*cos(a),.302*sin(a))),.003,alu,sides=5)
|
||||
for a,b in [(rear,saddle),(saddle,crank),(crank,rear),(saddle,head),(crank,head),(head,front)]: rod('Bicycle frame tube',a,b,.022,m,sides=12)
|
||||
box('Bicycle saddle',(bx,saddle.y,z+.69),(.19,.27,.05),black,.04)
|
||||
rod('Bicycle handlebar',(bx-.26,head.y+.04,z+.81),(bx+.26,head.y+.04,z+.81),.014,alu,sides=12)
|
||||
rod('Bicycle stem',head,(bx,head.y+.04,z+.81),.015,alu,sides=12)
|
||||
rod('Bicycle crank',(bx-.15,crank.y,crank.z),(bx+.15,crank.y,crank.z),.014,steel,sides=12)
|
||||
for x in [-.65,.25,-1.55]:
|
||||
for xx in [x-.075,x+.075]: rod('BIKE RACK wheel guide',(xx,-17.47,GF+.03),(xx,-16.97,GF+.03),.025,steel,sides=12)
|
||||
rod('Bike rack upright',(x,-16.98,GF+.03),(x,-16.98,GF+.5),.025,steel,sides=12)
|
||||
box('Bike rack shared base',(-.65,-17.22,GF+.03),(2.45,.58,.055),steel,.01)
|
||||
# Non-destructive presentation cutaways: retain wall geometry but use near-transparent skin materials.
|
||||
cutawaycache={}; cutobjects=[]
|
||||
def transparent_side(o):
|
||||
if o.type!='MESH': return
|
||||
original=[]
|
||||
for slot in o.material_slots:
|
||||
m=slot.material
|
||||
if not m: continue
|
||||
original.append(m.name)
|
||||
if m.name not in cutawaycache:
|
||||
col=tuple(m.diffuse_color[:3]); cutawaycache[m.name]=ghost('VIEW transparent / '+m.name,col,.055)
|
||||
slot.material=cutawaycache[m.name]
|
||||
o['Original wall materials']=json.dumps(original); o['Presentation only']='Transparent viewing facade. Original solid-wall material names preserved here.'
|
||||
# Hide filled wall faces in fast solid viewport, leaving structural posts and opening frames visible.
|
||||
o.hide_set(True); cutobjects.append(o.name)
|
||||
for cc in list(o.users_collection): cc.objects.unlink(o)
|
||||
COL['cutaway'].objects.link(o)
|
||||
for name in ['01 Living pavilion 12 x 6 m','02 Sleeping pavilion 8 x 5 m','23 NOW - Quiet meditation room','16 Oval Finnish sauna','06 Kitchen garden','28 Garage and workbench','20 Spacious site connections']:
|
||||
coll=bpy.data.collections.get(name)
|
||||
if not coll: continue
|
||||
for o in list(coll.objects):
|
||||
n=o.name; p=center(o); use=False
|
||||
if name.startswith('01'): use=n.startswith('Sliding glass pane')
|
||||
elif name.startswith('02'): use=n.startswith(('Bedroom north wall','Bedroom north lintel','Sliding glass pane'))
|
||||
elif name.startswith('23'): use=n.startswith(('Meditation room north clay wall','Meditation room north window infill','Meditation room north openable glazing'))
|
||||
elif name.startswith('16'): use=n.startswith(('Sauna front timber cheek','Sauna entrance oval infill','Sauna continuous tempered-glass','Sauna outward-opening'))
|
||||
elif name.startswith('06'): use=('Greenhouse end glazing' in n and p.y>-6) or ('Greenhouse side glazing' in n and p.x>24)
|
||||
elif name.startswith('28'): use=n.startswith(('Garage north front panel','Garage north pedestrian door','Garage north narrow infill','Garage roller door concept'))
|
||||
elif name.startswith('20'): use=n.startswith('Corridor north wall') or (n.startswith('Meditation room north wall roof junction'))
|
||||
if use: transparent_side(o)
|
||||
# Fix all newly created solid normals, including stock helpers inherited from the first model.
|
||||
for coll in COL.values():
|
||||
for o in coll.objects:
|
||||
if o.type=='MESH':
|
||||
bm=bmesh.new(); bm.from_mesh(o.data); bmesh.ops.recalc_face_normals(bm,faces=list(bm.faces)); bm.to_mesh(o.data); bm.free()
|
||||
# Replace obsolete children-room chapters with the meditation retreat, garage and future shadows.
|
||||
cam=bpy.data.objects['PLAY TOUR camera']; data=cam.data; tour=bpy.data.collections['22 PLAY - guided camera tour']; caption=bpy.data.materials['Tour captions ivory']
|
||||
chapters=json.load(open(os.path.join(OUT,'../samford-spacious-tour/tour-chapters.json')))[:17]
|
||||
for o in list(tour.objects):
|
||||
if o.type=='FONT' and int(o.name[:2])>=18: bpy.data.objects.remove(o,do_unlink=True)
|
||||
for m in list(s.timeline_markers):
|
||||
if m.frame>=2041: s.timeline_markers.remove(m)
|
||||
newshots={
|
||||
17:('18 / QUIET MEDITATION ROOM',(-8.4,16.5,3.8),(-9,15.8,3.4),(-11,11.7,1.65),(-11,11.5,1.65),32),
|
||||
18:('19 / CUSHION, CANDLES & STILLNESS',(-9.6,13.5,2.7),(-10,13.1,2.55),(-11,11.3,1.45),(-11,11.0,1.45),27),
|
||||
19:('20 / GRAVEL ROAD & GARAGE',(17,-4,10),(13,-7,8),(-3,-18,1.5),(-3,-18,1.5),36),
|
||||
20:('21 / CAR, WORKBENCH & BIKE RACKS',(-.5,-11,4.0),(-1.4,-12,3.6),(-2.3,-19,1.5),(-2.3,-19.4,1.5),30),
|
||||
21:('22 / FUTURE SPACES - BUILD LATER',(19,28,13),(16,25,11),(2,16,1.5),(2,16,1.5),37),
|
||||
22:('23 / A HOME THAT CAN CHANGE',(44,64,47),(49,69,50),(-3,-4,1),(-3,-4,1),40),
|
||||
}
|
||||
for i,(title,a,b,ta,tb,lens) in newshots.items():
|
||||
start=i*120+1; end=(i+1)*120
|
||||
for f,pos,target in [(start,a,ta),(end,b,tb)]:
|
||||
cam.location=pos; 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); data.lens=lens; data.keyframe_insert('lens',frame=f)
|
||||
s.timeline_markers.new(title,frame=start)
|
||||
cu=bpy.data.curves.new(title,'FONT'); cu.body=title; cu.size=.036; ob=bpy.data.objects.new(title,cu); tour.objects.link(ob); ob.parent=cam; half=36/lens; ob.location=(-half*.91,half*9/16*.84,-2); ob.show_in_front=True; cu.materials.append(caption)
|
||||
for f,val in [(0,0),(start-1,0),(start,1),(end,1),(end+1,0)]: ob.scale=(val,)*3; ob.keyframe_insert('scale',frame=f)
|
||||
chapters.append({'title':title,'start':start,'end':end,'seconds':5})
|
||||
def curves(action):
|
||||
if hasattr(action,'fcurves'): return action.fcurves
|
||||
result=[]
|
||||
for layer in action.layers:
|
||||
for strip in layer.strips:
|
||||
for slot in action.slots:
|
||||
bag=strip.channelbag(slot)
|
||||
if bag: result.extend(bag.fcurves)
|
||||
return result
|
||||
for owner in [cam,data]:
|
||||
for fc in curves(owner.animation_data.action):
|
||||
for k in fc.keyframe_points:k.interpolation='LINEAR'
|
||||
for o in tour.objects:
|
||||
if o.type=='FONT':
|
||||
for fc in curves(o.animation_data.action):
|
||||
for k in fc.keyframe_points:k.interpolation='CONSTANT'
|
||||
s.frame_end=2760; s.frame_set(1); s.camera=cam; s.sync_mode='FRAME_DROP'
|
||||
s['PLAY THE TOUR']='Space over 3D view or Timeline Play. Shift+Left Arrow restarts. 115 seconds / 23 chapters / 24 fps.'
|
||||
s['Current design']='One meditation retreat with cushion and candles; two unbuilt shadow spaces. Detached 8x7m garage with car, workbench and bicycle racks. Southeast gravel driveway. Transparent viewing facades are presentation-only.'
|
||||
s['Future additions']='Two faint unbuilt shadow rooms replace solid child rooms. Meditation cabin can be adapted later.'
|
||||
s['Transparent walls']='Collection 30 stores near-transparent wall skins. Filled faces hidden only in solid viewport. Structural frames stay visible. Original materials recorded on each object.'
|
||||
txt=bpy.data.texts['START HERE - PRESS PLAY']; txt.clear(); txt.write(s['PLAY THE TOUR']+'\n\n'+s['Current design']+'\n\n'+s['Transparent walls'])
|
||||
names=[o.name for o in s.objects]
|
||||
report={'future_shadow_spaces':2,'meditation_cushions':sum(n.startswith('ROUND MEDITATION CUSHION') for n in names),'candles':sum(n.startswith('Meditation candle') and 'wick' not in n for n in names),'garage_size_metres':[8,7],'cars':sum(n.startswith('ONE FAMILY CAR') for n in names),'bicycles':2,'rack_positions':3,'rainwater_tanks':sum(n.startswith('2000 mm') for n in names),'solar_panels':sum(n.startswith('PV panel glass and cells') for n in names),'garden_beds':sum(n.startswith('Raised vegetable bed soil') for n in names),'tour_seconds':115,'chapters':len(chapters),'transparent_wall_objects':len(cutobjects)}
|
||||
assert report['meditation_cushions']==1 and report['cars']==1 and report['rainwater_tanks']==2 and report['solar_panels']==30 and report['garden_beds']==6 and report['transparent_wall_objects']>20
|
||||
json.dump(report,open(os.path.join(OUT,'design-checks.json'),'w'),indent=2); json.dump(chapters,open(os.path.join(OUT,'tour-chapters.json'),'w'),indent=2)
|
||||
s.render.engine='CYCLES'; s.render.resolution_x=2560; s.render.resolution_y=1440; s.render.resolution_percentage=100; s.cycles.samples=40; s.cycles.use_denoising=True
|
||||
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'samford-retreat-garage-tour.blend'))
|
||||
# Fast chapter checks are separate from the saved model's render configuration.
|
||||
for o in COL['cutaway'].objects:o.hide_render=True
|
||||
for o in s.objects:
|
||||
if o.name=='Subtle atmosphere over hillside':o.hide_render=True
|
||||
s.render.engine='BLENDER_WORKBENCH'; s.render.resolution_percentage=30; s.display.shading.light='STUDIO'; s.display.shading.color_type='MATERIAL'; s.display.shading.show_shadows=True; s.display.shading.show_cavity=True
|
||||
for i in [0,6,9,17,18,19,20,21,22]:
|
||||
s.frame_set(i*120+60); s.render.filepath=os.path.join(OUT,'check-%02d.png'%(i+1)); bpy.ops.render.render(write_still=True)
|
||||
print('RETREAT_GARAGE_DRAFT_COMPLETE',flush=True)
|
||||
Reference in New Issue
Block a user