Files
samford-valley-home/blender/scripts/create_site_plan.py

65 lines
4.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from pathlib import Path
from html import escape
import json
O=Path(__file__).parent
p=['<svg xmlns="http://www.w3.org/2000/svg" width="1400" height="1120" viewBox="0 0 1400 1120"><rect width="1400" height="1120" fill="#f1eee5"/><style>text{font-family:Arial,sans-serif;fill:#263a34}</style><text x="60" y="60" font-size="32" font-weight="600">A home for movement, connection and quiet.</text><text x="60" y="95" font-size="17">SAMFORD VALLEY / Updated concept plan · 10 September 2026 · Version 003</text>']
def pt(x,y):return (630+13*x,470-13*y)
def label(x,y,lines,size=12):
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='#526154',dash=''):
X,Y=pt(cx-w/2,cy+d/2);p.append(f'<rect x="{X}" y="{Y}" width="{w*13}" height="{d*13}" fill="{fill}" stroke="{stroke}" stroke-width="2" {dash}/>')
def line(points,color,width):
pts=' '.join(f'{pt(x,y)[0]},{pt(x,y)[1]}' for x,y in points);p.append(f'<polyline points="{pts}" fill="none" stroke="{color}" stroke-width="{width}" stroke-linejoin="round"/>')
layout=json.loads((O/'daily-life-layout.json').read_text())
line(layout['walking_loop'],'#adc49a',14)
line([(14,1),(13,4),(13,7),(6,7)],'#adc49a',14)
line([(-1,24),(-7,21),(-14,18)],'#adc49a',14)
label(9,27.4,['SHADED WALKING LOOP'],12)
line([(36,-40),(28,-33),(22,-27),(8,-26),(5,-22),(5,-13),(1,-12),(-3,-12)],'#c9bca1',52)
rect(-1,-12.5,12,6,'#c9bca1',stroke='#c9bca1')
line([(-3,-10),(-2,-6),(-5.5,-4),(-6.5,-2.8)],'#d5cdbb',12)
line([(-3,5.4),(-3,9),(-11,9)],'#b5a88c',16)
rect(0,0,12,6,'#ddc8a9');label(1,.7,['SHARED HOME','Kitchen · dining'],10)
rect(0,4.2,12,2.4,'#c8d0b8');label(0,4.3,['VERANDA'],11)
rect(-7.75,-1.8,3.5,1.2,'#c7bfae');label(-7.8,1.5,['Short link'],10)
rect(-11.8,-4.9,8,5,'#ddc8a9');label(-11.8,-4.5,['PARENTS','Bedroom + bath'],11)
rect(-11,12,4.5,4.8,'#c9c1a5');label(-11,12.4,['QUIET','ROOM'],11)
X,Y=pt(-11,12);p.append(f'<circle cx="{X}" cy="{Y+24}" r="5" fill="#626257"/>')
for i,(x,y) in enumerate([(-3,17),(7,15)]):
rect(x,y,4.5,4.8,'#e6e8de',stroke='#98a394',dash='stroke-dasharray="5 5"');label(x,y+.6,['FUTURE',f'SPACE {i+1}'],11)
label(2,21.4,['Faint outlines only / build when needed'],12)
X,Y=pt(-23,2);p.append(f'<ellipse cx="{X}" cy="{Y}" rx="29" ry="25" fill="#bc9d75" stroke="#526154" stroke-width="2"/>');label(-23,2,['SAUNA'],10)
rect(-27,4.6,1.3,1.3,'#b6c4c5');label(-27,7.5,['Outdoor shower'],11)
for x in [-23.95,-26.25]:
X,Y=pt(x,-8);p.append(f'<circle cx="{X}" cy="{Y}" r="13" fill="#6b7f61"/>')
label(-25,-10.5,['Rainwater tanks'],11)
rect(-3,-19,8,7,'#bcb9ac');label(-3,-18.6,['GARAGE','8 × 7 m'],12)
rect(-4.7,-19.1,1.9,4.35,'#788c83');rect(-.47,-21.5,2.5,.76,'#ab9673')
label(-3,-24,['One car · workbench · bike racks'],12)
label(20,-25,['4 m gravel access road'],13)
rect(24.2,-7.5,4.3,5,'#c3d6c1');label(24.2,-7.4,['GREENHOUSE'],9)
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.5,['FOOD GARDEN'],12)
rect(13.2,-17.7,7.3,5.2,'#71848b');label(13.2,-17.2,['GROUND SOLAR'],10)
label(34,-42,['Site approach'],12)
rect(*layout['training'],'#b9c8ad');label(-12.2,-16.8,['COVERED','TRAINING','4.8 × 5.4 m'],10)
line([(-8.65,-17),(-8.4,-15),(-6.4,-13.4)],'#d5cdbb',12)
label(-15.8,-23.2,['Bar · rings · weights','Bench · movable mats'],11)
rect(*layout['outdoor_table'],'#d9c19b');rect(10,3.5,3.2,1.03,'#ab8962')
rect(10,4.48,2.95,.43,'#bda078');rect(10,2.52,2.95,.43,'#bda078')
rect(6.5,4.2,1,1.5,'#c8d0b8');label(10,9,['OUTDOOR TABLE','10 places + tea counter'],11)
rect(*layout['table_tennis'],'#ced8c4');rect(11,-8.5,1.525,2.74,'#608570')
label(11,-5.9,['TABLE TENNIS'],10);label(11,-12.8,['Folding table / level deck'],10)
line([(7.1,-7),(7,-4),(6.4,1.3),(7.1,3.5)],'#d5cdbb',12)
rect(*layout['future_basketball'],'#eceee4',stroke='#81947c',dash='stroke-dasharray="7 7"')
label(13,-35,['FUTURE','BASKETBALL','10 × 10 m reserve'],11)
label(13,-43,['Unbuilt / separate from driveway'],11)
rect(30,10.9,1.8,.45,'#a58a66');label(32,14,['QUIET BENCH'],11)
rect(29.8,1.5,.78,2.05,'#a58a66');label(35,2.4,['HARVEST','BENCH'],11)
label(4,-4.3,['Breakfast · pantry · freezer'],10)
rect(-3.8,.1,2.2,.95,'#f4f1e9')
p.extend(['<path d="M1250 220V153m0 0l-9 18m9-18l9 18" fill="none" stroke="#263a34" stroke-width="3"/><text x="1250" y="140" text-anchor="middle" font-size="17">N</text>','<line x1="70" y1="970" x2="200" y2="970" stroke="#263a34" stroke-width="4"/><text x="70" y="999" font-size="13">10 metres</text>','<line x1="60" y1="1040" x2="1340" y2="1040" stroke="#bdc3b6"/><text x="60" y="1070" font-size="15">The Blender presentation fades one facade of each building so you can see inside. Walls remain in the model.</text>','<text x="60" y="1096" font-size="13">Concept layout only. The road approach is illustrative; no surveyed property boundary or approved access is implied.</text></svg>'])
(O/'retreat-garage-plan.svg').write_text(''.join(p))