Files
samford-valley-home/source/samford-spacious-tour/refine_adjacent_bedroom.py

115 lines
9.9 KiB
Python

import bpy, math, os, json
from mathutils import Vector
from math import sin,cos,pi
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)
d=Vector((7.2,10.6,0))
for name in ['02 Sleeping pavilion 8 x 5 m','21 Bathroom fittings']:
for o in bpy.data.collections[name].objects:
if name.startswith('21') and o.type=='LIGHT' and o.name.startswith('Living'): continue
o.location+=d
for o in bpy.data.collections['18 Starlink and single-server homelab'].objects:
if o.name.startswith('Starlink'): o.location+=d
for o in bpy.data.collections['19 Clay finishes and hemp wall infill'].objects:
if o.name.startswith('Sleeping'): o.location+=d
for o in bpy.data.collections['12 Natural materials and reversible connections'].objects:
p=center(o)
if o.name.startswith('Sleeping west expansion') or (o.name.startswith('Stone facing bed joint') and p.x<-16 and p.y<-11): o.location+=d
for o in bpy.data.collections['05 Water and services'].objects:
if o.name.startswith('Exposed rainwater downpipe') and center(o).x<-15: o.location+=d
for o in list(bpy.data.collections['20 Spacious site connections'].objects):
if any(o.name.startswith(n) for n in ['Extended','Walkway','Sleeping pavilion garden walk']): bpy.data.objects.remove(o,do_unlink=True)
elif o.name.startswith('Optional extension link') and center(o).x<0: bpy.data.objects.remove(o,do_unlink=True)
# Main common room remains undivided; the private bedroom becomes fully enclosed.
for o in list(bpy.data.collections['02 Sleeping pavilion 8 x 5 m'].objects):
if o.name=='Bedroom bathroom partition': bpy.data.objects.remove(o,do_unlink=True)
COL={'room':bpy.data.collections['21 Bathroom fittings'],'link':bpy.data.collections['20 Spacious site connections']}; active='link'
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']; roof=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']; F=1.12
# Short enclosed link: west door of central room to north entrance of bedroom wing.
box('SHORT CORRIDOR - 3.5m long x 1.2m wide',(-7.75,-1.8,F-.08),(3.5,1.2,.16),wood,.012)
box('Short corridor insulated roof',(-7.75,-1.8,F+2.52),(3.65,1.37,.10),roof,.008)
box('Short corridor north clay wall',(-7.75,-1.16,F+1.25),(3.5,.12,2.50),clay,.005)
box('Short corridor south clay wall',(-6.95,-2.44,F+1.25),(1.60,.12,2.50),clay,.005)
box('Short corridor west return',(-9.56,-1.8,F+1.25),(.12,1.4,2.50),clay,.005)
for x in [-9.4,-6.2]:
for y in [-2.31,-1.29]:
g=ground(x,y); box('Short corridor stone pier',(x,y,(g+F-.16)/2),(.25,.25,F-.16-g),stone,.01)
box('Short corridor exposed post',(x,y,F+1.25),(.085,.085,2.50),woodz,.005)
for x in [-9.2,-8.4,-7.6,-6.8]: box('Short corridor exposed ceiling rafter',(x,-1.8,F+2.46),(.06,1.2,.12),wood)
# High glazing in north wall admits daylight while preserving privacy.
cut=box('TEMP corridor high light cutter',(-7.75,-1.16,F+2.07),(2.65,.5,.55),None)
wall=bpy.data.objects['Short corridor north clay wall']; mod=wall.modifiers.new('Clerestory opening','BOOLEAN'); mod.object=cut; mod.operation='DIFFERENCE'; bpy.context.view_layer.objects.active=wall; bpy.ops.object.modifier_apply(modifier=mod.name); bpy.data.objects.remove(cut,do_unlink=True)
win=box('Short corridor high daylight glazing',(-7.75,-1.16,F+2.07),(2.61,.018,.51),glass); win.display_type='WIRE'
for z in [F+1.80,F+2.34]: box('Corridor clerestory timber rail',(-7.75,-1.16,z),(2.73,.08,.055),wood)
# Privacy doors to two separate rooms, both reached from a shared entrance lobby.
active='room'
box('Bedroom privacy partition south',(-9.6,-5.79,F+1.34),(.15,3.22,2.68),clay,.006)
box('Bedroom privacy partition north return',(-9.6,-2.51,F+1.34),(.15,.22,2.68),clay,.006)
box('Bedroom privacy door lintel',(-9.6,-3.29,F+2.50),(.15,1.35,.32),clay)
door=box('BEDROOM - independent solid timber door',(-9.6,-3.28,F+1.13),(.05,1.02,2.26),wood,.008)
for y in [-3.82,-2.74]: box('Bedroom privacy door jamb',(-9.6,y,F+1.16),(.19,.06,2.32),woodz,.006)
rod('Bedroom door lever',(-9.66,-2.92,F+1.06),(-9.78,-2.92,F+1.06),.015,steel)
box('Bathroom lobby partition east',(-7.99,-3.85,F+1.34),(.37,.14,2.68),clay)
box('Bathroom lobby partition west',(-9.40,-3.85,F+1.34),(.40,.14,2.68),clay)
box('Bathroom lobby partition header',(-8.72,-3.85,F+2.49),(1.02,.14,.34),clay)
box('BATHROOM - separate door from lobby',(-8.71,-3.85,F+1.12),(.95,.05,2.24),wood,.006)
rod('Bathroom door lever',(-8.38,-3.81,F+1.05),(-8.20,-3.81,F+1.05),.015,steel)
# Relocate wet fixtures deeper than the shared lobby, keeping the toilet out of the common-room sightline.
for o in COL['room'].objects:
if o.name.startswith('Bathroom shower'):
o.location.y-=1.15
if o.name=='Bathroom mineral floor': o.location.y=-5.70; o.dimensions.y=3.18
# A wardrobe buffers the bed from the bathroom plumbing wall.
for y in [-6.55,-5.70,-4.85]:
box('Bedroom wardrobe cabinet',(-10.06,y,F+1.12),(.52,.81,2.24),wood,.009)
box('Bedroom wardrobe discreet handle',(-10.34,y+.27,F+1.15),(.028,.025,.20),steel,.005)
# Move dining slightly into the visual centre without changing generous kitchen circulation.
for o in bpy.data.collections['10 Furniture'].objects:
if o.name.startswith(('Dining table','Timber dining chair','Chair leg','Chair back')): o.location.x+=.30
# Replace long-route chapter with short lobby views and reposition bedroom/bathroom/Starlink shots.
cam=bpy.data.objects['PLAY TOUR camera']; data=cam.data
changes={
5:('06 / SHORT CORRIDOR TO PRIVATE ROOMS',(-5.6,-1.8,2.78),(-6.05,-1.8,2.72),(-8.7,-1.8,2.1),(-8.9,-2.3,2.1),24),
6:('07 / ADJACENT BEDROOM WING',(-1,9,8),(-3,7,7),(-10,-3.5,2),(-11,-4,2),34),
7:('08 / ENCLOSED BEDROOM',(-10.9,-3.3,2.9),(-11.2,-3.5,2.75),(-13.9,-5.2,1.8),(-13.9,-5.4,1.8),27),
8:('09 / BATHROOM - SEPARATE LOBBY DOOR',(-9.0,-4.15,2.85),(-8.98,-4.35,2.75),(-8.52,-6.4,1.65),(-8.52,-6.55,1.65),22),
13:('14 / ROOFTOP SOLAR & STARLINK',(3,7,10),(0,6,9),(-4,-1,4.3),(-6,-1.5,4.3),35),
}
for i,(title,a,b,ta,tb,lens) in changes.items():
start=i*120+1; end=(i+1)*120
for f,p,t in [(start,a,ta),(end,b,tb)]:
cam.location=p; cam.rotation_euler=(Vector(t)-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)
for marker in s.timeline_markers:
if marker.frame==start: marker.name=title
for o in bpy.data.collections['22 PLAY - guided camera tour'].objects:
if o.type=='FONT' and o.name.startswith('%02d /'%(i+1)):
o.data.body=title; o.name=title; half=36/lens; o.location=(-half*.91,half*9/16*.84,-2)
# Keep caption viewport state; disable the misleading legacy long-gap description.
s['Spacious layout']='Final revision: central kitchen/dining/living pavilion; enclosed adjacent bedroom wing via a short 3.5m x 1.2m corridor and shared lobby. Bathroom has its own door from lobby. Sauna, tanks, garden and future dwellings remain spread out.'
s['Adjoining pavilion explanation']='Adjacent bedroom wing contains a private bedroom, a shared entrance lobby and separately accessed bathroom. It is not a detached toilet block.'
s['Architecture rationale']='North-facing common room and veranda retained; short circulation; solid privacy doors; wardrobe between bed and wet-service wall; existing openable facade retained. Site-specific structural, moisture, fire and access design still required.'
for name in ['Sleeping ceiling light','Bathroom ceiling light']:
o=bpy.data.objects.get(name)
if o: o.location.y-=.6
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_set(1); s.camera=cam; s.render.engine='CYCLES'; s.render.resolution_percentage=100
checks=json.load(open(os.path.join(OUT,'tour-checks.json'))); checks.pop('north_south_clear_gap_metres',None); checks.update({'sleeping_center':[-11.8,-4.9],'short_corridor_metres':[3.5,1.2],'bedroom_separate_room':True,'bathroom_access':'Own door from shared lobby; no passage through bedroom','layout_revision':'Adjacent private bedroom and central kitchen/dining; dispersed outbuildings'})
json.dump(checks,open(os.path.join(OUT,'tour-checks.json'),'w'),indent=2)
chapters=json.load(open(os.path.join(OUT,'tour-chapters.json')))
for i,v in changes.items(): chapters[i]['title']=v[0]
json.dump(chapters,open(os.path.join(OUT,'tour-chapters.json'),'w'),indent=2)
txt=bpy.data.texts['START HERE - PRESS PLAY']; txt.clear(); txt.write(s['PLAY THE TOUR']+'\n\n'+s['Spacious layout']+'\n\n'+s['Adjoining pavilion explanation']+'\n\n'+s['Viewport performance'])
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'samford-spacious-guided-tour.blend'))
# Review every chapter with the final layout.
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(20):
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('ADJACENT_BEDROOM_TOUR_REVIEW_READY',flush=True)