152 lines
13 KiB
Python
152 lines
13 KiB
Python
import bpy, os, math, json, bmesh
|
|
from mathutils import Vector
|
|
from math import sin,cos,pi
|
|
OUT=os.path.dirname(os.path.abspath(__file__)); s=bpy.context.scene
|
|
COL={}
|
|
for key,name in [('one','23 FUTURE - Child 1 private room'),('two','24 FUTURE - Child 2 private room'),('three','25 FUTURE - Child 3 private room'),('links','26 Children courtyard and covered links')]:
|
|
c=bpy.data.collections.new(name); s.collection.children.link(c); COL[key]=c
|
|
active='links'; 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']; bamboo=bpy.data.materials['Bamboo lining - golden fibres']; linen=bpy.data.materials['Undyed flax linen']; F=1.12
|
|
def ground(x,y): return -.024*y+.004*x+.12*sin(x*.19)*cos(y*.14)+.055*sin(x*.72+y*.32)
|
|
for name in ['13 NEXT - optional attached room modules','14 LATER - independent dwelling and workshop']:
|
|
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(bpy.data.collections['20 Spacious site connections'].objects):
|
|
if o.name.startswith(('Optional extension link','Future north dwelling reserved access','Future workshop reserved access')): bpy.data.objects.remove(o,do_unlink=True)
|
|
# Repair the old west wall boolean so the short corridor is physically open to the main room.
|
|
wall=bpy.data.objects['Insulated end wall']; bm=bmesh.new(); bm.from_mesh(wall.data); bmesh.ops.recalc_face_normals(bm,faces=list(bm.faces)); bm.to_mesh(wall.data); bm.free()
|
|
cut=box('TEMP correct central-room doorway',(-6,-1.8,F+1.16),(.85,1.13,2.34),None)
|
|
mod=wall.modifiers.new('Open doorway into short bedroom corridor','BOOLEAN'); mod.operation='DIFFERENCE'; mod.solver='EXACT'; mod.object=cut; bpy.context.view_layer.objects.active=wall; bpy.ops.object.modifier_apply(modifier=mod.name); bpy.data.objects.remove(cut,do_unlink=True)
|
|
accents=[mat('Child 1 example clay accent',(.40,.20,.12),.85),mat('Child 2 example sage accent',(.25,.37,.26),.85),mat('Child 3 example blue accent',(.20,.31,.39),.85)]
|
|
cabins=[('one',-11,12),('two',-3,17),('three',7,15)]
|
|
for idx,(key,cx,cy) in enumerate(cabins):
|
|
active=key; w=4.5; dep=4.8; xs=cx-w/2; xe=cx+w/2; ys=cy-dep/2; yn=cy+dep/2; a=accents[idx]
|
|
box('Child %d | timber insulated floor'%(idx+1),(cx,cy,F-.11),(w,dep,.22),wood,.012)
|
|
for x in [xs+.20,xe-.20]:
|
|
for y in [ys+.20,yn-.20]:
|
|
g=ground(x,y); box('Child room stone pier',(x,y,(g+F-.22)/2),(.28,.28,F-.22-g),stone,.01)
|
|
box('Child room steel post shoe',(x,y,F-.23),(.18,.18,.09),steel,.005)
|
|
# South entry facade has a solid door and a shaded sidelight.
|
|
for x,ww in [(cx-1.42,1.66),(cx+1.42,1.66)]: box('Child room south clay wall',(x,ys,F+1.35),(ww,.17,2.7),clay)
|
|
box('Child room south door header',(cx,ys,F+2.49),(1.2,.17,.42),clay)
|
|
box('Child %d | independent front door'%(idx+1),(cx,ys-.05,F+1.14),(1.04,.05,2.28),wood,.009)
|
|
for x in [cx-.56,cx+.56]: box('Child room entry timber jamb',(x,ys-.07,F+1.17),(.07,.12,2.34),woodz,.005)
|
|
rod('Child room door handle',(cx+.39,ys-.1,F+1.1),(cx+.25,ys-.1,F+1.1),.015,steel)
|
|
# North wall window and east window give two sides for operable ventilation.
|
|
for x,ww in [(cx-1.7,1.10),(cx+1.7,1.10)]: box('Child room north clay wall',(x,yn,F+1.5),(ww,.17,3.0),clay)
|
|
for z,hh in [(F+.45,.9),(F+2.75,.50)]: box('Child room north window infill',(cx,yn,z),(2.32,.17,hh),clay)
|
|
box('Child room north openable glazing',(cx,yn,F+1.7),(2.24,.02,1.55),glass)
|
|
for x in [cx-1.16,cx+1.16]: box('Child room north window frame',(x,yn,F+1.7),(.055,.12,1.64),woodz)
|
|
for z in [F+.88,F+2.52]: box('Child room north window rail',(cx,yn,z),(2.36,.12,.06),wood)
|
|
box('Child room west clay wall',(xs,cy,F+1.43),(.17,dep,2.86),clay)
|
|
for y,dd in [(cy-1.68,1.44),(cy+1.68,1.44)]: box('Child room east clay wall',(xe,y,F+1.43),(.17,dd,2.86),clay)
|
|
for z,hh in [(F+.5,1),(F+2.59,.54)]: box('Child room east window infill',(xe,cy,z),(.17,1.92,hh),clay)
|
|
box('Child room east openable glazing',(xe,cy,F+1.66),(.018,1.90,1.3),glass)
|
|
# Exterior timber weatherboards, kept outside the window apertures.
|
|
for j in range(18):
|
|
z=F+.075+j*.15
|
|
for x,ww in [(cx-1.44,1.57),(cx+1.44,1.57)]: box('Child room front weatherboard',(x,ys-.11,z),(ww,.045,.142),weather)
|
|
box('Child room west weatherboard',(xs-.11,cy,z),(.045,dep,.142),weather)
|
|
for x in [xs,xe]:
|
|
for y,h in [(ys,2.75),(yn,3.1)]: box('Child room exposed corner post',(x,y,F+h/2),(.10,.10,h),woodz,.005)
|
|
for i in range(8):
|
|
x=xs+i*w/7; beam('Child room exposed roof rafter',(x,ys-.3,F+2.73),(x,yn+.3,F+3.13),.055,.15,wood)
|
|
for i in range(33):
|
|
x=xs+(i+.5)*w/33; beam('Child room bamboo ceiling',(x,ys,F+2.85),(x,yn,F+3.20),w/33-.004,.02,bamboo)
|
|
ro=box('Child room simple mono-pitch roof',(cx,cy,F+3.03),(w+.65,dep+.65,.07),roofmat,.008); ro.rotation_euler.x=math.atan(.35/dep)
|
|
box('Child room covered entry deck',(cx,ys-.65,F-.035),(2.2,1.3,.07),wood,.009)
|
|
canopy=box('Child room entry awning',(cx,ys-.65,F+2.55),(2.4,1.5,.065),roofmat,.01)
|
|
# A complete private room with freely replaceable decor.
|
|
bx=cx-1.20; by=cy+.25
|
|
box('Child %d | single bed base'%(idx+1),(bx,by,F+.21),(1.05,2.10,.36),wood,.02)
|
|
box('Child room mattress',(bx,by,F+.45),(1.0,2.02,.19),linen,.06)
|
|
box('Child room sample coloured blanket',(bx,by-.30,F+.555),(1.01,1.18,.035),a,.025)
|
|
box('Child room pillow',(bx,by+.72,F+.60),(.70,.40,.12),linen,.06)
|
|
box('Child room desk',(cx+.90,cy+.82,F+.75),(1.58,.62,.055),wood,.01)
|
|
for x in [cx+.22,cx+1.58]: box('Child room desk leg',(x,cy+.82,F+.37),(.05,.51,.74),steel)
|
|
box('Child room desk stool',(cx+.9,cy+.12,F+.44),(.40,.40,.055),wood,.01)
|
|
for x in [cx+.74,cx+1.06]:
|
|
for y in [cy-.04,cy+.28]: box('Child room stool leg',(x,y,F+.21),(.03,.03,.42),wood)
|
|
box('Child room wardrobe',(cx+1.60,cy-1.54,F+1.0),(.66,.72,2.0),wood,.012)
|
|
box('Child room personal pinboard',(xs+.10,cy-.15,F+1.75),(.035,1.35,.77),a,.01)
|
|
for j in range(3): box('Child room replaceable display shelf',(cx+.90,yn-.20,F+1.35+j*.37),(1.6,.28,.025),wood)
|
|
cu=bpy.data.curves.new('Child room sign','FONT'); cu.body='CHILD %d / YOUR SPACE'%(idx+1); cu.size=.085; cu.align_x='CENTER'; ob=bpy.data.objects.new(cu.body,cu); COL[active].objects.link(ob); ob.location=(cx,ys-.145,F+2.4); ob.rotation_euler=(pi/2,0,0); cu.materials.append(steel)
|
|
ld=bpy.data.lights.new('Child room warm ceiling light','AREA'); ld.energy=120; ld.size=2; ld.color=(1,.86,.7); lo=bpy.data.objects.new(ld.name,ld); COL[active].objects.link(lo); lo.location=(cx,cy,F+2.6)
|
|
COL[active]['Purpose']='Future private bedroom for one child, not a separate self-contained dwelling. Shared kitchen/dining and bathroom in the family home. Decor shown is replaceable example only.'
|
|
active='links'
|
|
for cx,cy,w,dep in [(-3,10,1.2,9.2),(-7,9,8,1.2),(2,9,10,1.2),(7,10.8,1.2,3.6)]:
|
|
box('Children shared covered walk deck',(cx,cy,F-.07),(w,dep,.14),wood,.008)
|
|
box('Children shared covered walk roof',(cx,cy,F+2.55),(w+.14,dep+.14,.055),roofmat,.008)
|
|
n=max(2,int(max(w,dep)/2.7)+1)
|
|
for j in range(n):
|
|
for side in [-1,1]:
|
|
x=cx-w/2+.10+j*(w-.20)/(n-1) if w>dep else cx+side*(w/2-.08)
|
|
y=cy+side*(dep/2-.08) if w>dep else cy-dep/2+.10+j*(dep-.20)/(n-1)
|
|
g=ground(x,y); box('Children walkway post',(x,y,(g+F+2.52)/2),(.075,.075,F+2.52-g),woodz,.004)
|
|
# Courtyard-facing directions keep the children connected to the family common room.
|
|
cam=bpy.data.objects['PLAY TOUR camera']; data=cam.data
|
|
newshots={
|
|
17:('18 / CHILD 1 - THEIR OWN ROOM',(-10.15,10.30,2.9),(-10.35,10.5,2.8),(-11.45,12.5,1.8),(-11.5,12.7,1.8),24),
|
|
18:('19 / CHILD 2 - THEIR OWN ROOM',(-2.15,15.30,2.9),(-2.35,15.5,2.8),(-3.45,17.5,1.8),(-3.5,17.7,1.8),24),
|
|
19:('20 / CHILD 3 - THEIR OWN ROOM',(7.85,13.30,2.9),(7.65,13.5,2.8),(6.55,15.5,1.8),(6.5,15.7,1.8),24),
|
|
20:('21 / THREE PRIVATE ROOMS - ONE FAMILY',(18,-1,14),(15,2,12),(-2,13,2),(-2,13,2),37),
|
|
21:('22 / LIVE FIRST. GROW TOGETHER.',(44,64,47),(51,71,51),(-4,0,1),(-4,0,1),40),
|
|
}
|
|
tour=bpy.data.collections['22 PLAY - guided camera tour']; caption=bpy.data.materials['Tour captions ivory']
|
|
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 marker in list(s.timeline_markers):
|
|
if marker.frame>=2041: s.timeline_markers.remove(marker)
|
|
chapters=json.load(open(os.path.join(OUT,'tour-chapters.json')))[:17]
|
|
for i,(title,a,b,ta,tb,lens) in newshots.items():
|
|
start=i*120+1; end=(i+1)*120
|
|
for frame,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=frame); cam.keyframe_insert('rotation_euler',frame=frame); data.lens=lens; data.keyframe_insert('lens',frame=frame)
|
|
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'
|
|
# Correct normals on newly made solids; outline transparent glass in fast viewport mode.
|
|
for c in COL.values():
|
|
for o in c.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()
|
|
for o in s.objects:
|
|
if o.type=='MESH' and any(sl.material==glass for sl in o.material_slots): o.display_type='WIRE'
|
|
s.frame_end=2640; s.frame_set(1); s.camera=cam; s.sync_mode='FRAME_DROP'
|
|
s['PLAY THE TOUR']='Press Space over the 3D viewport or click Timeline Play. Shift+Left Arrow returns to frame 1. 110 seconds, 22 chapters, 24 fps. Saved in camera view.'
|
|
s['Future additions']='Exactly three separate children bedroom cabins, one for each child. 4.5m x 4.8m footprints, private doors, bed, desk, wardrobe and personal display area. Covered walks connect them to the shared home. No extra kitchens/bathrooms in the cabins.'
|
|
s['Land planning']='Concept can be investigated on roughly 1-2 hectares of usable land; 2 hectares is a comfortable search target, not a site-specific sufficiency or planning guarantee. No cadastral boundaries or survey supplied.'
|
|
txt=bpy.data.texts['START HERE - PRESS PLAY']; txt.clear(); txt.write(s['PLAY THE TOUR']+'\n\n'+s['Spacious layout']+'\n\n'+s['Future additions']+'\n\n'+s['Land planning']+'\n\n'+s['Viewport performance'])
|
|
json.dump(chapters,open(os.path.join(OUT,'tour-chapters.json'),'w'),indent=2)
|
|
report=json.load(open(os.path.join(OUT,'tour-checks.json'))); report.update({'duration_seconds':110,'frames':2640,'shots':22,'future_child_rooms':3,'child_room_footprints':[4.5,4.8],'generic_future_office_guest_workshop_removed':True}); json.dump(report,open(os.path.join(OUT,'tour-checks.json'),'w'),indent=2)
|
|
s.render.engine='CYCLES'; s.render.resolution_percentage=100
|
|
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'samford-spacious-guided-tour.blend'))
|
|
# All final chapter thumbnails, plus motion endpoints for the corridor and children rooms.
|
|
for o in s.objects:
|
|
if o.type=='MESH' and any(sl.material==glass for sl in o.material_slots): o.hide_render=True
|
|
if o.name=='Subtle atmosphere over hillside': o.hide_render=True
|
|
if o.type=='MESH' and o.name.startswith(('Native grasses','Eucalyptus individual canopy leaves','Mango / avocado individual canopy leaves')): 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 range(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('FAMILY_TOUR_FINAL_REVIEW_READY',flush=True)
|