Preserve homestead context and garden-centred Blender design
This commit is contained in:
39
source/samford-spacious-tour/README.md
Normal file
39
source/samford-spacious-tour/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Samford family homestead — press Play
|
||||
|
||||
Open **samford-spacious-guided-tour.blend**. It is saved at frame 1 in the animated camera view.
|
||||
|
||||
Place the pointer over the 3D view and press **Space**, or click the Timeline’s Play triangle. Space pauses. **Shift + Left Arrow** returns to the beginning. If you leave camera view, use **View → Cameras → Active Camera** before playing.
|
||||
|
||||
The tour lasts **110 seconds**, with **22 chapters**. Each chapter uses a short camera move, with deliberate cuts between rooms. It includes the main living pavilion, kitchen, dining, homelab, short corridor, parents’ bedroom, bathroom, sauna exterior and interior, outdoor shower, tanks, Starlink, solar array, garden, greenhouse, each child’s future room, courtyard and overall site. Chapter labels and Timeline markers are included.
|
||||
|
||||
## Final family layout
|
||||
|
||||
- Kitchen, dining and lounge form the central shared room.
|
||||
- The parents’ enclosed bedroom is adjacent, reached by a short corridor approximately 3.5m long and 1.2m wide.
|
||||
- The bathroom has its own door from the shared lobby. Visitors do not pass through the parents’ bedroom.
|
||||
- Three future private children’s rooms replace the earlier generic office, guest, workshop and dwelling reservations. Each has a 4.5 × 4.8m footprint, its own front door, bed, desk, wardrobe and personal display area. Colour accents are replaceable examples, with no prescribed theme for any child.
|
||||
- Covered timber walks connect the three rooms around a courtyard to the shared home. They have no separate kitchens or bathrooms.
|
||||
- The sauna, shower, two tanks, greenhouse and six garden beds remain spread out. The 30 solar panels and one local-AI server are retained.
|
||||
|
||||
These future children’s rooms are fully modelled so their interiors can be toured. Collections 23, 24 and 25 contain them individually; collection 26 contains their shared walks. They are future design proposals, not a claim that they are already built or approved. Their roof-junction finishing strips are in collection 20.
|
||||
|
||||
## Files and display
|
||||
|
||||
- `family-homestead-overview.png`: rendered overview.
|
||||
- `three-childrens-rooms.png`: courtyard and children’s rooms.
|
||||
- `child-room-interior.png`: example adaptable interior.
|
||||
- `family-courtyard-plan.svg`: labelled layout diagram.
|
||||
- `tour-chapters.json`: chapter names and frame ranges.
|
||||
- `tour-checks.json`: verified scene counts, timing, moving cameras and clear corridor doorway.
|
||||
|
||||
The interactive tour uses solid material colours for responsiveness. Glass appears outlined in that viewport mode; the Cycles renderer retains actual glazing and procedural materials. Dense ground grass is hidden in the viewport only. The original earlier models are preserved in their own output folders.
|
||||
|
||||
## Land search guidance
|
||||
|
||||
For two adults and three children wanting this layout, vegetables, fruit trees and general outdoor space, **1–2 hectares of usable land is a reasonable starting search range; about 2 hectares (4.94 acres) is my comfortable planning target**. This is a design estimate, not a statutory minimum or a promise of food self-sufficiency. More land may suit grazing animals or substantial privacy buffers, but pasture quality, water, labour and management become decisive.
|
||||
|
||||
A relatively level, usable hectare can offer more practical room than a larger constrained property. Check the actual address for slope, easements, access, wastewater capacity and hazards. [YourHome site selection](https://www.yourhome.gov.au/buy-build-renovate/choosing-site) explains the role of site constraints. [City of Moreton Bay flood mapping](https://www.moretonbay.qld.gov.au/Services/Property-Ownership/Flooding/Flood-Mapping) distinguishes current flood information from planning overlay maps. [Queensland’s small-property livestock guide](https://era.daf.qld.gov.au/id/eprint/8160/1/a-guide-to-managing-livestock-on-small-properties.pdf) explains why livestock capacity is site-dependent.
|
||||
|
||||
The detached children’s sleeping rooms need project-specific building and planning classification. Do not assume that describing them as bedrooms makes approval automatic, or that a large lot permits multiple independent dwellings. [Council’s secondary-dwelling guidance](https://www.moretonbay.qld.gov.au/Services/Building-Development/Planning-Schemes/MBRC/Info-Sheets/Secondary-Dwelling) illustrates that dwelling type, zoning and siting all matter; it is not a determination of how this particular bedroom-cabin proposal would be classified.
|
||||
|
||||
The central shared room, short private circulation and separation of bedrooms from noisy uses draw on [YourHome orientation guidance](https://www.yourhome.gov.au/passive-design/orientation) and [noise-control guidance](https://www.yourhome.gov.au/live-adapt/noise-control). This remains an architectural concept without a surveyed site, detailed structural design, certified access, fire, moisture, water or off-grid energy assessment.
|
||||
212
source/samford-spacious-tour/build_tour.py
Normal file
212
source/samford-spacious-tour/build_tour.py
Normal file
@@ -0,0 +1,212 @@
|
||||
import bpy, math, os, json, random, bmesh
|
||||
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)
|
||||
def move(o,delta): o.location+=Vector(delta)
|
||||
def shift_collection(name,d):
|
||||
for o in bpy.data.collections[name].objects: move(o,d)
|
||||
sleep=(-5,-7,0); sauna=(-5,-2,0); tanks=(-5,-4,0); garden=(6,1,0)
|
||||
shift_collection('02 Sleeping pavilion 8 x 5 m',sleep)
|
||||
shift_collection('16 Oval Finnish sauna',sauna)
|
||||
shift_collection('17 Outdoor shower and stone terrace',sauna)
|
||||
shift_collection('06 Kitchen garden',garden)
|
||||
for o in bpy.data.collections['05 Water and services'].objects:
|
||||
p=center(o); n=o.name
|
||||
if n.startswith(('2000 mm','Poly tank','Tank service lid','Accessible tank outlet','Gravel tank pad')): move(o,tanks)
|
||||
elif n.startswith('Exposed rainwater downpipe') and p.x<-10: move(o,sleep)
|
||||
elif p.y<-8 and ('PV ' in n or 'Photovoltaic' in n or 'Ground array' in n): move(o,(3,-6,0))
|
||||
for o in bpy.data.collections['18 Starlink and single-server homelab'].objects:
|
||||
if o.name.startswith('Starlink'): move(o,sleep)
|
||||
if o.name=='Overview sauna label': o.hide_render=True; o.hide_set(True)
|
||||
for o in bpy.data.collections['19 Clay finishes and hemp wall infill'].objects:
|
||||
if o.name.startswith('Sleeping'): move(o,sleep)
|
||||
for o in list(bpy.data.collections['12 Natural materials and reversible connections'].objects):
|
||||
p=center(o)
|
||||
if o.name.startswith('Sleeping west expansion'): move(o,sleep)
|
||||
elif o.name.startswith('Stone facing bed joint'):
|
||||
if p.x<-12 and p.y<-5: move(o,sleep)
|
||||
elif p.x<-6: bpy.data.objects.remove(o,do_unlink=True)
|
||||
# Guest and office are optional attached rooms; short connecting frames will be rebuilt.
|
||||
for o in list(bpy.data.collections['13 NEXT - optional attached room modules'].objects):
|
||||
if o.name.startswith('02A'): move(o,(5,0,0))
|
||||
elif o.name.startswith('02B'): move(o,(-7,-7,0))
|
||||
else: bpy.data.objects.remove(o,do_unlink=True)
|
||||
for o in list(bpy.data.collections['14 LATER - independent dwelling and workshop'].objects):
|
||||
if o.name.startswith('03A'): move(o,(0,9,0))
|
||||
elif o.name.startswith('03B'): move(o,(0,-8,0))
|
||||
else: bpy.data.objects.remove(o,do_unlink=True)
|
||||
for o in bpy.data.collections['15 Expansion labels - toggle to hide'].objects: o.hide_set(True); o.hide_render=True
|
||||
# Move the tree whose former location is inside the newly separated guest reservation.
|
||||
tree_delta=(0,0,0)
|
||||
for o in bpy.data.collections['09 Eucalyptus and fruit trees'].objects:
|
||||
if 'trunk' in o.name:
|
||||
p=center(o); tree_delta=(-7,0,0) if -35<p.x<-33 and -17<p.y<-13 else (0,0,0)
|
||||
if 'Fruit tree' in o.name: tree_delta=garden
|
||||
if tree_delta!=(0,0,0): move(o,tree_delta)
|
||||
# Remove old hard-edged routes and rebuild a connected, terrain-following gravel network.
|
||||
for o in list(bpy.data.collections['07 Terrain and paths'].objects):
|
||||
if 'path' in o.name.lower() or 'gravel' in o.name.lower(): bpy.data.objects.remove(o,do_unlink=True)
|
||||
for o in list(bpy.data.collections['17 Outdoor shower and stone terrace'].objects):
|
||||
if o.name.startswith('Loose gravel sauna access'): bpy.data.objects.remove(o,do_unlink=True)
|
||||
for o in list(bpy.data.collections['03 Covered breezeway'].objects): bpy.data.objects.remove(o,do_unlink=True)
|
||||
COL={}; active='new'
|
||||
for key,name in [('new','20 Spacious site connections'),('bath','21 Bathroom fittings'),('tour','22 PLAY - guided camera tour')]:
|
||||
c=bpy.data.collections.new(name); s.collection.children.link(c); COL[key]=c
|
||||
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']; gravel=bpy.data.materials['Loose local gravel - fine stones']; clay=bpy.data.materials['Soft warm clay plaster - natural mineral texture']; glass=bpy.data.materials['Clear glazing']; F=1.12
|
||||
for cx,cy,w,d in [(-11.1,-1.8,10.2,1.6),(-16.2,-7.45,1.6,11.3)]:
|
||||
box('Extended covered walkway bearer',(cx,cy,F-.18),(w,d,.18),wood)
|
||||
for i in range(int(w/.14)):
|
||||
box('Extended walkway timber deck board',(cx-w/2+.07+i*.14,cy,F-.035),(.13,d,.06),wood,.003)
|
||||
box('Extended walkway pale roof',(cx,cy,F+2.7),(w+.20,d+.20,.065),roof,.008)
|
||||
count=max(2,int(max(w,d)/2.4)+1)
|
||||
for j in range(count):
|
||||
a=j/(count-1)
|
||||
for side in [-1,1]:
|
||||
x=cx-w/2+.12+a*(w-.24) if w>d else cx+side*(w/2-.12)
|
||||
y=cy+side*(d/2-.12) if w>d else cy-d/2+.12+a*(d-.24)
|
||||
g=ground(x,y); box('Walkway stone pier',(x,y,(g+F-.25)/2),(.27,.27,F-.25-g),stone,.01)
|
||||
box('Walkway exposed timber post',(x,y,F+1.32),(.10,.10,2.64),woodz,.005)
|
||||
if w>d:
|
||||
for j in range(int(w/.6)+1): box('Walkway exposed rafter',(cx-w/2+j*.6,cy,F+2.64),(.055,d,.10),wood)
|
||||
else:
|
||||
for j in range(int(d/.6)+1): box('Walkway exposed rafter',(cx,cy-d/2+j*.6,F+2.64),(w,.055,.10),wood)
|
||||
nextmat=bpy.data.materials['NEXT - warm timber ghost frame']
|
||||
for x1,x2,y in [(6.25,12.8,.8),(-27,-23.25,-15.2)]:
|
||||
for yy in [y-.8,y+.8]:
|
||||
beam('Optional extension link floor',(x1,yy,F),(x2,yy,F),.07,.10,nextmat)
|
||||
beam('Optional extension link roof',(x1,yy,F+2.75),(x2,yy,F+2.75),.07,.10,nextmat)
|
||||
def path(name,points,width):
|
||||
vs=[]; fs=[]
|
||||
for a,b in zip(points,points[1:]):
|
||||
av,bv=Vector(a),Vector(b); dv=bv-av; side=Vector((-dv.y,dv.x)).normalized()*width/2
|
||||
n=max(2,int(dv.length/.4))
|
||||
for i in range(n):
|
||||
p=av+dv*i/(n-1)
|
||||
for sidep in [p-side,p+side]: vs.append((sidep.x,sidep.y,ground(sidep.x,sidep.y)+.045))
|
||||
k=len(vs)-n*2
|
||||
fs.extend([(k+i*2,k+i*2+1,k+i*2+3,k+i*2+2) for i in range(n-1)])
|
||||
mesh(name,vs,fs,gravel)
|
||||
path('Main meandering gravel arrival',[(1,17),(0,10),(0,7)],1.6)
|
||||
path('Sauna and tanks gravel walk',[(0,7),(-8,6),(-15,6),(-22,6.6),(-25,5.9),(-27,2),(-27,-5),(-24,-8)],1.2)
|
||||
path('Garden and greenhouse gravel walk',[(0,7),(9,9),(19,9),(26,6),(26,-4),(24,-5)],1.25)
|
||||
path('Sleeping pavilion garden walk',[(-8,6),(-12,2),(-12,-11),(-15.5,-12.5)],1.1)
|
||||
path('Future north dwelling reserved access',[(-8,6),(-7,15),(-9,21)],.8)
|
||||
path('Future workshop reserved access',[(0,-3.5),(0,-18),(0,-25)],.8)
|
||||
# Stretch ground-contact supports to the actual ground after relocation.
|
||||
for o in s.objects:
|
||||
if o.type!='MESH': continue
|
||||
if any(t in o.name.lower() for t in ['pier','foundation support','tank pad']):
|
||||
p=center(o); zmin=min((o.matrix_world@v.co).z for v in o.data.vertices); g=ground(p.x,p.y)
|
||||
if g<max((o.matrix_world@v.co).z for v in o.data.vertices)-.05:
|
||||
inv=o.matrix_world.inverted()
|
||||
for v in o.data.vertices:
|
||||
q=o.matrix_world@v.co
|
||||
if abs(q.z-zmin)<.001: q.z=g; v.co=inv@q
|
||||
# Complete the bathroom alcove so the interior tour can identify its actual purpose.
|
||||
active='bath'; ceramic=mat('Bathroom warm white ceramic',(.76,.73,.65),.23); water=mat('Bathroom satin stainless',(.34,.38,.38),.32,.75)
|
||||
box('Bathroom mineral floor',(-15.94,-15.92,F+.02),(1.50,3.85,.045),stone,.008)
|
||||
box('Bathroom toilet cistern',(-15.83,-17.74,F+.65),(.44,.19,.65),ceramic,.08)
|
||||
box('Bathroom toilet pedestal',(-15.83,-17.38,F+.22),(.29,.40,.43),ceramic,.10)
|
||||
box('Bathroom toilet bowl',(-15.83,-17.29,F+.43),(.43,.59,.15),ceramic,.15)
|
||||
box('Bathroom toilet seat',(-15.83,-17.25,F+.52),(.40,.55,.055),ceramic,.12)
|
||||
box('Bathroom floating timber vanity',(-15.50,-15.7,F+.64),(.45,.82,.28),wood,.02)
|
||||
box('Bathroom basin',(-15.50,-15.7,F+.85),(.42,.65,.11),ceramic,.055)
|
||||
rod('Bathroom tap',(-15.30,-15.8,F+.9),(-15.30,-15.8,F+1.13),.015,water)
|
||||
box('Bathroom mirror',(-15.12,-15.7,F+1.51),(.022,.72,.8),water,.025)
|
||||
box('Bathroom shower tray',(-15.93,-14.0,F+.04),(1.45,1.18,.08),stone,.012)
|
||||
rod('Bathroom shower riser',(-15.15,-13.97,F+.35),(-15.15,-13.97,F+2.15),.017,water)
|
||||
rod('Bathroom shower arm',(-15.15,-13.97,F+2.15),(-15.58,-13.97,F+2.15),.017,water)
|
||||
rod('Bathroom shower head',(-15.58,-13.97,F+2.10),(-15.58,-13.97,F+2.14),.10,water,sides=24)
|
||||
# Gentle interior fill lights are visible fixtures, rather than relying on outdoor sunlight.
|
||||
for name,p,power in [('Living ceiling light',(-2,-.5,3.7),220),('Sleeping ceiling light',(-19,-15,3.7),130),('Bathroom ceiling light',(-15.9,-15.4,3.55),65)]:
|
||||
d=bpy.data.lights.new(name,'AREA'); d.energy=power; d.size=2; d.color=(1,.85,.68); ob=bpy.data.objects.new(name,d); COL[active].objects.link(ob); ob.location=p
|
||||
# One animated camera with explicit shot-boundary cuts is dependable in Timeline playback.
|
||||
active='tour'; s.timeline_markers.clear()
|
||||
camdata=bpy.data.cameras.new('PLAY TOUR camera'); cam=bpy.data.objects.new('PLAY TOUR camera',camdata); COL[active].objects.link(cam); camdata.clip_start=.04; camdata.clip_end=450; camdata.lens=34; s.camera=cam
|
||||
shots=[
|
||||
('01 / THE SPACIOUS HOMESTEAD',(51,71,48),(45,65,43),(-4,-3,1),(-5,-4,1),44),
|
||||
('02 / LIVING PAVILION & VERANDA',(12,17,6),(8,12,5),(0,3,2),(0,2.8,2),36),
|
||||
('03 / KITCHEN & EXPOSED STRUCTURE',(-.2,1.8,2.9),(-1.2,1.25,2.75),(-3,-2.35,2.0),(-3.2,-2.35,2.1),30),
|
||||
('04 / DINING & OPEN LIVING',(-4.8,1.8,2.9),(-4.1,2,2.7),(.2,.1,1.9),(.9,-.1,2),30),
|
||||
('05 / ONE SERVER - LOCAL AI',(1,-.85,3.55),(1.25,-1,3.3),(3.9,-2.3,1.95),(4.2,-2.3,1.9),36),
|
||||
('06 / LONG COVERED WALK',(-7.2,-.8,2.75),(-9.5,-.8,2.75),(-16.2,-2.4,2),(-16.2,-4,2),30),
|
||||
('07 / BEDROOM + BATHROOM PAVILION',(-8,-5,7),(-10,-7,6),(-19,-15.5,2),(-19,-15.5,2),35),
|
||||
('08 / THE BEDROOM',(-17.6,-13.8,2.8),(-17.9,-14,2.7),(-20,-15.8,1.7),(-20,-16,1.7),26),
|
||||
('09 / THE BATHROOM',(-16.25,-14.9,2.95),(-16.15,-15.1,2.9),(-15.7,-17,1.7),(-15.7,-17.1,1.7),22),
|
||||
('10 / FINNISH SAUNA & OUTDOOR SHOWER',(-14,15,6.5),(-16,12,5.5),(-24,2.7,1.7),(-24,2.6,1.6),37),
|
||||
('11 / SAUNA BENCHES & STONE HEATER',(-23.0,3.62,2.18),(-23.25,3.62,2.18),(-23,1.2,1.95),(-23.2,1.2,1.95),20),
|
||||
('12 / OUTDOOR SHOWER',(-24.5,7,2.9),(-25,6.5,2.7),(-27,4.4,1.4),(-27,4.4,1.4),34),
|
||||
('13 / TWO RAINWATER TANKS',(-18,-1,4.4),(-19,-2.5,4.1),(-25,-8,1),(-25,-8,1),40),
|
||||
('14 / ROOFTOP SOLAR & STARLINK',(-4,-3,10),(-7,-5,9),(-15.5,-13.1,4.6),(-15.5,-13.1,4.6),36),
|
||||
('15 / SOLAR ARRAY & ENERGY STORAGE',(22,-7,7),(20,-9,6),(13,-17.5,1.5),(13,-17.5,1.5),36),
|
||||
('16 / FOOD GARDEN & GREENHOUSE',(34,10,9),(32,7,7),(25,-3,1),(25,-4,1),34),
|
||||
('17 / INSIDE THE GREENHOUSE',(24.2,-5.7,2.6),(24.2,-6.3,2.5),(24.2,-9.0,1.3),(24.2,-9.5,1.3),24),
|
||||
('18 / ADD ROOMS WHEN NEEDED',(26,12,11),(22,10,10),(15.8,1,2),(15.8,1,2),37),
|
||||
('19 / FUTURE DWELLING & WORKSHOP',(48,69,51),(44,64,47),(-6,-2,1),(-6,-2,1),40),
|
||||
('20 / LIVE FIRST. ADD LATER.',(44,64,47),(51,71,51),(-6,-2,1),(-6,-2,1),40),
|
||||
]
|
||||
FPS=24; N=120; s.render.fps=FPS; s.frame_start=1; s.frame_end=N*len(shots); s.sync_mode='FRAME_DROP'
|
||||
caption=mat('Tour captions ivory',(.92,.94,.86),.6); caption.node_tree.nodes['Principled BSDF'].inputs['Emission Color'].default_value=(.92,.94,.86,1); caption.node_tree.nodes['Principled BSDF'].inputs['Emission Strength'].default_value=1
|
||||
schedule=[]
|
||||
for i,(title,a,b,ta,tb,lens) in enumerate(shots):
|
||||
start=i*N+1; end=(i+1)*N
|
||||
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); camdata.lens=lens; camdata.keyframe_insert('lens',frame=frame)
|
||||
marker=s.timeline_markers.new(title,frame=start)
|
||||
cu=bpy.data.curves.new(title,'FONT'); cu.body=title; cu.size=.036; cu.align_x='LEFT'; ob=bpy.data.objects.new(title,cu); COL[active].objects.link(ob); cu.materials.append(caption); ob.parent=cam
|
||||
half=36/lens; ob.location=(-half*.91,half*(9/16)*.84,-2); ob.show_in_front=True
|
||||
for f,value in [(0,0),(start-1,0),(start,1),(end,1),(end+1,0)]: ob.scale=(value,)*3; ob.keyframe_insert('scale',frame=f)
|
||||
schedule.append({'title':title,'start':start,'end':end,'seconds':5})
|
||||
# Linear moves within a shot; constant title visibility changes.
|
||||
def curves(action):
|
||||
if not action: return []
|
||||
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,camdata]:
|
||||
for fc in curves(owner.animation_data.action):
|
||||
for k in fc.keyframe_points: k.interpolation='LINEAR'
|
||||
for o in COL['tour'].objects:
|
||||
if o.type=='FONT':
|
||||
for fc in curves(o.animation_data.action):
|
||||
for k in fc.keyframe_points: k.interpolation='CONSTANT'
|
||||
# Viewport tour uses material colours for responsive playback. Glazing is outlined so interiors remain visible.
|
||||
for o in s.objects:
|
||||
if o.type=='MESH' and any(sl.material==glass for sl in o.material_slots): o.display_type='WIRE'
|
||||
if o.type=='MESH' and o.name.startswith(('Native grasses','Eucalyptus individual canopy leaves','Mango / avocado individual canopy leaves')): o.hide_set(True)
|
||||
for screen in bpy.data.screens:
|
||||
for area in screen.areas:
|
||||
if area.type=='VIEW_3D':
|
||||
sp=area.spaces.active; sp.region_3d.view_perspective='CAMERA'; sp.region_3d.view_camera_zoom=0; sp.shading.type='SOLID'; sp.shading.color_type='MATERIAL'; sp.shading.light='STUDIO'; sp.shading.show_shadows=True; sp.shading.show_cavity=True; sp.overlay.show_overlays=False; sp.clip_end=450
|
||||
if hasattr(sp,'use_local_camera'): sp.use_local_camera=False
|
||||
for area in screen.areas:
|
||||
if area.type=='DOPESHEET_EDITOR': area.spaces.active.show_region_ui=False
|
||||
s.frame_set(1); 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
|
||||
s['PLAY THE TOUR']='Press Space with the mouse over the 3D viewport, or click Play in Timeline. Shift+Left Arrow returns to the beginning. 100 seconds / 20 scenes at 24 fps. Saved in active camera view.'
|
||||
s['Spacious layout']='Sleeping pavilion moved 5m west and 7m south: 10m north-south clear gap from living footprint. Covered L walkway rebuilt. Sauna moved 5m west/2m south; tanks 5m west/4m south; garden 6m east/1m north. Optional future sites spaced farther apart.'
|
||||
s['Adjoining pavilion explanation']='The second rectangle is the bedroom and bathroom pavilion, not a standalone toilet. Bathroom fixtures now modelled for the tour.'
|
||||
s['Viewport performance']='Dense leaf/grass meshes are hidden only in viewport, retained for final renders. Glass uses outline display in solid viewport. Restore via Alt+H if desired.'
|
||||
txt=bpy.data.texts.new('START HERE - PRESS PLAY'); txt.write(s['PLAY THE TOUR']+'\n\n'+s['Spacious layout']+'\n\n'+s['Adjoining pavilion explanation']+'\n\n'+s['Viewport performance'])
|
||||
json.dump(schedule,open(os.path.join(OUT,'tour-chapters.json'),'w'),indent=2)
|
||||
names=[o.name for o in s.objects]
|
||||
report={'duration_seconds':100,'frames':s.frame_end,'shots':len(shots),'tank_count':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),'servers':sum(n.startswith('ONE LOCAL AI SERVER') for n in names),'sleeping_center':[-19,-15.5],'north_south_clear_gap_metres':10,'active_camera':cam.name,'bathroom_fitted':True}
|
||||
assert report['tank_count']==2 and report['solar_panels']==30 and report['garden_beds']==6 and report['servers']==1
|
||||
json.dump(report,open(os.path.join(OUT,'tour-checks.json'),'w'),indent=2)
|
||||
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'samford-spacious-guided-tour.blend'))
|
||||
# Contact sheets can be made from quick workbench renders without waiting for a full movie.
|
||||
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; s.display.shading.background_type='WORLD'; s.world.color=(.19,.23,.22)
|
||||
for i in range(len(shots)):
|
||||
s.frame_set(i*N+60); s.render.filepath=os.path.join(OUT,'check-%02d.png'%(i+1)); bpy.ops.render.render(write_still=True)
|
||||
print('TOUR_BUILT_AND_CHAPTERS_RENDERED',flush=True)
|
||||
151
source/samford-spacious-tour/children_and_final_tour.py
Normal file
151
source/samford-spacious-tour/children_and_final_tour.py
Normal file
@@ -0,0 +1,151 @@
|
||||
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)
|
||||
40
source/samford-spacious-tour/create_family_plan.py
Normal file
40
source/samford-spacious-tour/create_family_plan.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from pathlib import Path
|
||||
from html import escape
|
||||
O=Path(__file__).parent
|
||||
p=['<svg xmlns="http://www.w3.org/2000/svg" width="1400" height="1080" viewBox="0 0 1400 1080"><rect width="1400" height="1080" fill="#f1eee5"/><style>text{font-family:Arial,sans-serif;fill:#263a34}.small{font-size:13px}.body{font-size:16px}.title{font-size:32px;font-weight:600}.label{font-size:13px;font-weight:600}</style><text x="60" y="60" class="title">One family. Room to become yourself.</text><text x="60" y="94" class="body">SAMFORD VALLEY / Shared kitchen + dining, adjacent parents’ room, three future children’s rooms</text>']
|
||||
def pt(x,y):return (690+17*x,520-17*y)
|
||||
def label(x,y,lines,size=13):
|
||||
X,Y=pt(x,y)
|
||||
for i,t in enumerate(lines):p.append(f'<text x="{X}" y="{Y+i*(size+3)}" text-anchor="middle" font-size="{size}">{escape(t)}</text>')
|
||||
def rect(cx,cy,w,d,fill,stroke='#4b6256',dash=''):
|
||||
X,Y=pt(cx-w/2,cy+d/2);p.append(f'<rect x="{X}" y="{Y}" width="{w*17}" height="{d*17}" rx="2" fill="{fill}" stroke="{stroke}" stroke-width="2" {dash}/>')
|
||||
def line(points,color='#a99372',width=16):
|
||||
ps=' '.join(f'{pt(x,y)[0]},{pt(x,y)[1]}' for x,y in points);p.append(f'<polyline points="{ps}" fill="none" stroke="{color}" stroke-width="{width}" stroke-linejoin="round"/>')
|
||||
# Covered walks first, with clear building footprints drawn over them.
|
||||
line([(-3,5.4),(-3,14.6)],width=20);line([(-11,9), (7,9),(7,12.6)],width=20)
|
||||
rect(0,0,12,6,'#ddc8a9');label(0,.8,['CENTRAL ROOM','Kitchen · dining · lounge','Local AI desk'],13)
|
||||
rect(0,4.2,12,2.4,'#c8d0b8');label(0,4.3,['NORTH VERANDA'],12)
|
||||
rect(-7.75,-1.8,3.5,1.2,'#c7bfae');label(-7.9,1.0,['Short corridor','3.5 × 1.2 m'],12)
|
||||
rect(-11.8,-4.9,8,5,'#ddc8a9');rect(-12.7,-4.9,6.2,5,'#ddc8a9');label(-12.7,-4.5,['PARENTS’','BEDROOM'],12)
|
||||
rect(-8.7,-5.63,1.8,3.54,'#b6c4c5');label(-8.7,-5.25,['Bath'],10);label(-8.7,-3.0,['Lobby'],9)
|
||||
for i,(x,y,color) in enumerate([(-11,12,'#d8b4a0'),(-3,17,'#b9c9b8'),(7,15,'#aec4d0')]):
|
||||
rect(x,y,4.5,4.8,color,dash='stroke-dasharray="6 3"');label(x,y+.6,[f'CHILD {i+1}','Own room','4.5 × 4.8 m'],11)
|
||||
label(-1,11.6,['SHARED','COURTYARD'],12)
|
||||
X,Y=pt(-23,2);p.append(f'<ellipse cx="{X}" cy="{Y}" rx="38" ry="32" fill="#c5a378" stroke="#4b6256" stroke-width="2"/>');label(-23,2.1,['SAUNA'],12)
|
||||
rect(-27,4.6,1.3,1.3,'#b6c4c5');label(-27,7,['Outdoor','shower'],12)
|
||||
for x in [-23.95,-26.25]:
|
||||
X,Y=pt(x,-8);p.append(f'<circle cx="{X}" cy="{Y}" r="17" fill="#6b7f61"/>')
|
||||
label(-25,-10,['Rainwater tanks'],12)
|
||||
rect(24.2,-7.5,4.3,5,'#c3d6c1');label(24.2,-7,['GREENHOUSE'],11)
|
||||
for x in [23.8,27.5]:
|
||||
for y in [-1.7,1.5,4.7]:rect(x,y,2.3,1.3,'#b29b73')
|
||||
label(25.5,7,['FOOD GARDEN'],13)
|
||||
rect(13.2,-17.7,7.3,5.2,'#71848b');label(13.2,-17,['GROUND SOLAR'],12)
|
||||
label(0,-7.5,['Roof solar on main pavilion','Starlink on parents’ wing'],12)
|
||||
line([(0,7),(9,9),(19,9),(26,6)],color='#d2ccb9',width=7)
|
||||
line([(-3,5.4),(-15,6),(-22,6.6),(-25,5.9)],color='#d2ccb9',width=7)
|
||||
# Diagram furniture indicates that children personalise the same simple base room.
|
||||
for x,y in [(-11,12),(-3,17),(7,15)]:
|
||||
rect(x-1.4,y-.7,.7,1.5,'#f3eadb',stroke='#879387')
|
||||
p.extend(['<path d="M1260 215V148m0 0l-9 18m9-18l9 18" fill="none" stroke="#263a34" stroke-width="3"/><text x="1260" y="135" text-anchor="middle" class="body">N</text>','<line x1="75" y1="890" x2="245" y2="890" stroke="#263a34" stroke-width="4"/><text x="75" y="920" class="small">10 metres</text>','<line x1="60" y1="960" x2="1340" y2="960" stroke="#bdc3b6"/><text x="60" y="992" class="body">Dashed cabins = future rooms for the three children. Shared kitchen, dining and bathroom stay in the family home.</text>','<text x="60" y="1020" class="small">Illustrative layout, not a surveyed block or construction drawing. No property boundary or approval is implied.</text>','<text x="60" y="1044" class="small">Land search guide: 1–2 hectares of usable land; around 2 hectares offers more flexibility. Site conditions decide suitability.</text></svg>'])
|
||||
(O/'family-courtyard-plan.svg').write_text(''.join(p))
|
||||
70
source/samford-spacious-tour/finalize_and_render.py
Normal file
70
source/samford-spacious-tour/finalize_and_render.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import bpy, os, math, bmesh, json
|
||||
from mathutils import Vector
|
||||
OUT=os.path.dirname(os.path.abspath(__file__)); s=bpy.context.scene
|
||||
c=bpy.data.collections['20 Spacious site connections']
|
||||
clay=bpy.data.materials['Soft warm clay plaster - natural mineral texture']; wood=bpy.data.materials['Unpainted hardwood - long X grain']
|
||||
def box(n,p,dims,mat):
|
||||
bpy.ops.mesh.primitive_cube_add(size=1,location=p); o=bpy.context.object; o.name=n; o.dimensions=dims; bpy.ops.object.transform_apply(location=False,rotation=False,scale=True)
|
||||
for col in list(o.users_collection): col.objects.unlink(o)
|
||||
c.objects.link(o); o.data.materials.append(mat); return o
|
||||
F=1.12
|
||||
# Explicit wall segments preserve a real, unobstructed doorway without boolean ambiguity.
|
||||
o=bpy.data.objects.get('Insulated end wall')
|
||||
if o: bpy.data.objects.remove(o,do_unlink=True)
|
||||
for ya,yb,zlo in [(-3,-2.365,F),(-1.235,3,F),(-2.365,-1.235,F+2.34)]:
|
||||
def top(y): return F+2.7+(y+3)*.7/6
|
||||
vs=[(x,y,z) for x in [-6.09,-5.91] for y,z in [(ya,zlo),(yb,zlo),(yb,top(yb)),(ya,top(ya))]]
|
||||
me=bpy.data.meshes.new('Living west wall with clear doorway'); me.from_pydata(vs,[],[(0,3,2,1),(4,5,6,7),(0,1,5,4),(1,2,6,5),(2,3,7,6),(3,0,4,7)]); me.materials.append(clay); ob=bpy.data.objects.new('Living west wall | clear corridor opening',me); c.objects.link(ob)
|
||||
o=bpy.data.objects.get('Short corridor north clay wall')
|
||||
if o: bpy.data.objects.remove(o,do_unlink=True)
|
||||
box('Corridor north wall below high window',(-7.75,-1.16,F+.895),(3.5,.12,1.79),clay)
|
||||
box('Corridor north wall above high window',(-7.75,-1.16,F+2.425),(3.5,.12,.15),clay)
|
||||
for x in [-9.31,-6.19]: box('Corridor north wall window end',(x,-1.16,F+2.07),(.38,.12,.56),clay)
|
||||
o=bpy.data.objects['Bedroom privacy partition south']; o.location.y=-5.61; o.dimensions.y=3.58
|
||||
o=bpy.data.objects['Bedroom privacy partition north return']; o.location.y=-2.57; o.dimensions.y=.34
|
||||
for x in [-9.2,-8.22]: box('Bathroom solid door jamb',(x,-3.85,F+1.14),(.05,.18,2.28),wood)
|
||||
# Close the sloping wall/roof junctions on each future child's enclosed room.
|
||||
for cx,cy in [(-11,12),(-3,17),(7,15)]:
|
||||
for xx in [cx-2.25,cx+2.25]:
|
||||
ya,yb=cy-2.4,cy+2.4
|
||||
vs=[(x,y,z) for x in [xx-.085,xx+.085] for y,z in [(ya,F+2.60),(yb,F+2.60),(yb,F+3.22),(ya,F+2.87)]]
|
||||
me=bpy.data.meshes.new('Sloping sealed child wall top'); me.from_pydata(vs,[],[(0,3,2,1),(4,5,6,7),(0,1,5,4),(1,2,6,5),(2,3,7,6),(3,0,4,7)]); me.materials.append(clay); ob=bpy.data.objects.new('Child room sealed sloping wall top',me); c.objects.link(ob)
|
||||
box('Child room north wall roof junction',(cx,cy+2.4,F+3.10),(4.5,.17,.22),clay)
|
||||
box('Child room south wall roof junction',(cx,cy-2.4,F+2.78),(4.5,.17,.17),clay)
|
||||
for ob in s.objects:
|
||||
if ob.type=='MESH' and len(ob.data.vertices)==8 and len(ob.data.polygons)==6:
|
||||
bm=bmesh.new(); bm.from_mesh(ob.data); bmesh.ops.recalc_face_normals(bm,faces=list(bm.faces)); bm.to_mesh(ob.data); bm.free()
|
||||
if ob.type=='MESH' and ob.name.startswith(('Eucalyptus individual canopy leaves','Mango / avocado individual canopy leaves')): ob.hide_set(False)
|
||||
cam=s.camera; data=cam.data
|
||||
for f,pos,target in [(601,(-6.6,-1.8,2.70),(-8.9,-2.05,2.13)),(720,(-7.7,-1.8,2.65),(-8.9,-2.7,2.10))]:
|
||||
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)
|
||||
# Update saved viewport state for Play and retain the leafy canopy in the interactive scene.
|
||||
for screen in bpy.data.screens:
|
||||
for area in screen.areas:
|
||||
if area.type=='VIEW_3D':
|
||||
area.spaces.active.region_3d.view_perspective='CAMERA'; area.spaces.active.shading.type='SOLID'; area.spaces.active.shading.color_type='MATERIAL'; area.spaces.active.overlay.show_overlays=False
|
||||
s['Viewport performance']='Material-colour camera tour. Dense grass is hidden only in the viewport; canopy remains visible. Glazing uses outlines in solid viewport and renders as glass in Cycles.'
|
||||
# Check the doorway's centre ray reaches the corridor interior, not a wall at its threshold.
|
||||
dg=bpy.context.evaluated_depsgraph_get(); start=Vector((-5.5,-1.8,2.3)); hit=s.ray_cast(dg,start,Vector((-1,0,0)),distance=4.2)
|
||||
assert not hit[0] or hit[1].x<-7.9, (hit[4].name,hit[1][:])
|
||||
report=json.load(open(os.path.join(OUT,'tour-checks.json'))); report['doorway_clear']=True
|
||||
positions=[]
|
||||
for ch in json.load(open(os.path.join(OUT,'tour-chapters.json'))):
|
||||
s.frame_set(ch['start']); a=list(cam.location); s.frame_set(ch['end']); b=list(cam.location); assert (Vector(a)-Vector(b)).length>.01; positions.append({'chapter':ch['title'],'start_camera':a,'end_camera':b})
|
||||
report['all_chapters_have_camera_motion']=True; report['render_resolution']=[2560,1440]; json.dump(report,open(os.path.join(OUT,'tour-checks.json'),'w'),indent=2)
|
||||
s.frame_set(1); s.render.engine='CYCLES'; s.render.resolution_percentage=100; s.cycles.samples=40; s.cycles.use_denoising=True; s.cycles.adaptive_threshold=.045
|
||||
prefs=bpy.context.preferences.addons['cycles'].preferences; prefs.compute_device_type='METAL'; prefs.get_devices()
|
||||
for dev in prefs.devices: dev.use=dev.type=='METAL'
|
||||
s.cycles.device='GPU'; s.render.filepath=os.path.join(OUT,'family-homestead-overview.png')
|
||||
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'samford-spacious-guided-tour.blend'))
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print('FAMILY_OVERVIEW_RENDERED',flush=True)
|
||||
s.frame_set(2460); s.render.filepath=os.path.join(OUT,'three-childrens-rooms.png'); bpy.ops.render.render(write_still=True)
|
||||
print('CHILDREN_COURTYARD_RENDERED',flush=True)
|
||||
s.frame_set(2160); s.render.filepath=os.path.join(OUT,'child-room-interior.png'); bpy.ops.render.render(write_still=True)
|
||||
print('CHILD_ROOM_INTERIOR_RENDERED',flush=True)
|
||||
# One last fast screenshot verifies the revised short-corridor chapter.
|
||||
for ob in s.objects:
|
||||
if ob.name=='Subtle atmosphere over hillside': ob.hide_render=True
|
||||
s.frame_set(660); s.render.engine='BLENDER_WORKBENCH'; s.render.resolution_percentage=30; s.display.shading.color_type='MATERIAL'; s.render.filepath=os.path.join(OUT,'corridor-check.png'); bpy.ops.render.render(write_still=True)
|
||||
print('FINAL_TOUR_VERIFIED',flush=True)
|
||||
6
source/samford-spacious-tour/inspect_scene.py
Normal file
6
source/samford-spacious-tour/inspect_scene.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import bpy,json
|
||||
from mathutils import Vector
|
||||
def center(o): return o.matrix_world@(sum((Vector(v) for v in o.bound_box),Vector())/8)
|
||||
report={c.name:[{'name':o.name,'center':[round(v,2) for v in center(o)],'location':[round(v,2) for v in o.location],'dimensions':[round(v,2) for v in o.dimensions]} for o in c.objects if o.type in ['MESH','CAMERA','LIGHT','FONT']] for c in bpy.data.collections}
|
||||
json.dump(report,open('/tmp/samford-scene-inventory.json','w'),indent=2)
|
||||
print({c.name:len(c.objects) for c in bpy.data.collections})
|
||||
114
source/samford-spacious-tour/refine_adjacent_bedroom.py
Normal file
114
source/samford-spacious-tour/refine_adjacent_bedroom.py
Normal file
@@ -0,0 +1,114 @@
|
||||
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)
|
||||
24
source/samford-spacious-tour/save_ready_to_play.py
Normal file
24
source/samford-spacious-tour/save_ready_to_play.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import bpy,os
|
||||
from mathutils import Vector
|
||||
OUT=os.path.dirname(os.path.abspath(__file__)); s=bpy.context.scene; cam=s.camera
|
||||
# A wider architectural view makes the short link easier to understand than a close doorway shot.
|
||||
for f,pos,target in [(601,(-13,3.0,5.7),(-8,-1.8,2.6)),(720,(-12,2.4,5.1),(-8,-1.8,2.6))]:
|
||||
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)
|
||||
# Six seats let the whole family gather around the central table.
|
||||
c=bpy.data.collections['10 Furniture']; wood=bpy.data.materials['Unpainted hardwood - long X grain']
|
||||
def box(n,p,sz):
|
||||
bpy.ops.mesh.primitive_cube_add(size=1,location=p); o=bpy.context.object; o.name=n; o.dimensions=sz; bpy.ops.object.transform_apply(location=False,rotation=False,scale=True)
|
||||
for col in list(o.users_collection): col.objects.unlink(o)
|
||||
c.objects.link(o); o.data.materials.append(wood); b=o.modifiers.new('Eased timber edge','BEVEL'); b.width=.007; b.segments=2
|
||||
for x,side in [(-1.46,-1),(1.66,1)]:
|
||||
box('Family dining end chair seat',(x,.3,1.55),(.46,.46,.045))
|
||||
for dx in [-.17,.17]:
|
||||
for dy in [-.17,.17]: box('Family dining end chair leg',(x+dx,.3+dy,1.33),(.038,.038,.42))
|
||||
box('Family dining end chair back',(x+side*.2,.3,1.82),(.035,.46,.32))
|
||||
s.frame_set(1); s.camera=cam; s.render.engine='CYCLES'; s.render.resolution_percentage=100; s.render.filepath=os.path.join(OUT,'family-homestead-overview.png')
|
||||
s['Family table']='Six dining seats around the shared central table.'
|
||||
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'samford-spacious-guided-tour.blend'))
|
||||
for o in s.objects:
|
||||
if o.name=='Subtle atmosphere over hillside': o.hide_render=True
|
||||
s.frame_set(660); s.render.engine='BLENDER_WORKBENCH'; s.render.resolution_percentage=30; s.display.shading.color_type='MATERIAL'; s.render.filepath=os.path.join(OUT,'corridor-check.png'); bpy.ops.render.render(write_still=True)
|
||||
print('READY_TO_PLAY_SAVED',flush=True)
|
||||
134
source/samford-spacious-tour/tour-chapters.json
Normal file
134
source/samford-spacious-tour/tour-chapters.json
Normal file
@@ -0,0 +1,134 @@
|
||||
[
|
||||
{
|
||||
"title": "01 / THE SPACIOUS HOMESTEAD",
|
||||
"start": 1,
|
||||
"end": 120,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "02 / LIVING PAVILION & VERANDA",
|
||||
"start": 121,
|
||||
"end": 240,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "03 / KITCHEN & EXPOSED STRUCTURE",
|
||||
"start": 241,
|
||||
"end": 360,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "04 / DINING & OPEN LIVING",
|
||||
"start": 361,
|
||||
"end": 480,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "05 / ONE SERVER - LOCAL AI",
|
||||
"start": 481,
|
||||
"end": 600,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "06 / SHORT CORRIDOR TO PRIVATE ROOMS",
|
||||
"start": 601,
|
||||
"end": 720,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "07 / ADJACENT BEDROOM WING",
|
||||
"start": 721,
|
||||
"end": 840,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "08 / ENCLOSED BEDROOM",
|
||||
"start": 841,
|
||||
"end": 960,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "09 / BATHROOM - SEPARATE LOBBY DOOR",
|
||||
"start": 961,
|
||||
"end": 1080,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "10 / FINNISH SAUNA & OUTDOOR SHOWER",
|
||||
"start": 1081,
|
||||
"end": 1200,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "11 / SAUNA BENCHES & STONE HEATER",
|
||||
"start": 1201,
|
||||
"end": 1320,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "12 / OUTDOOR SHOWER",
|
||||
"start": 1321,
|
||||
"end": 1440,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "13 / TWO RAINWATER TANKS",
|
||||
"start": 1441,
|
||||
"end": 1560,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "14 / ROOFTOP SOLAR & STARLINK",
|
||||
"start": 1561,
|
||||
"end": 1680,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "15 / SOLAR ARRAY & ENERGY STORAGE",
|
||||
"start": 1681,
|
||||
"end": 1800,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "16 / FOOD GARDEN & GREENHOUSE",
|
||||
"start": 1801,
|
||||
"end": 1920,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "17 / INSIDE THE GREENHOUSE",
|
||||
"start": 1921,
|
||||
"end": 2040,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "18 / CHILD 1 - THEIR OWN ROOM",
|
||||
"start": 2041,
|
||||
"end": 2160,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "19 / CHILD 2 - THEIR OWN ROOM",
|
||||
"start": 2161,
|
||||
"end": 2280,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "20 / CHILD 3 - THEIR OWN ROOM",
|
||||
"start": 2281,
|
||||
"end": 2400,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "21 / THREE PRIVATE ROOMS - ONE FAMILY",
|
||||
"start": 2401,
|
||||
"end": 2520,
|
||||
"seconds": 5
|
||||
},
|
||||
{
|
||||
"title": "22 / LIVE FIRST. GROW TOGETHER.",
|
||||
"start": 2521,
|
||||
"end": 2640,
|
||||
"seconds": 5
|
||||
}
|
||||
]
|
||||
34
source/samford-spacious-tour/tour-checks.json
Normal file
34
source/samford-spacious-tour/tour-checks.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"duration_seconds": 110,
|
||||
"frames": 2640,
|
||||
"shots": 22,
|
||||
"tank_count": 2,
|
||||
"solar_panels": 30,
|
||||
"garden_beds": 6,
|
||||
"servers": 1,
|
||||
"sleeping_center": [
|
||||
-11.8,
|
||||
-4.9
|
||||
],
|
||||
"active_camera": "PLAY TOUR camera",
|
||||
"bathroom_fitted": true,
|
||||
"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",
|
||||
"future_child_rooms": 3,
|
||||
"child_room_footprints": [
|
||||
4.5,
|
||||
4.8
|
||||
],
|
||||
"generic_future_office_guest_workshop_removed": true,
|
||||
"doorway_clear": true,
|
||||
"all_chapters_have_camera_motion": true,
|
||||
"render_resolution": [
|
||||
2560,
|
||||
1440
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user