Preserve homestead context and garden-centred Blender design

This commit is contained in:
2026-09-10 20:52:43 +10:00
commit c1369d9ed7
321 changed files with 17843 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
import bpy,math,random,os,json,bmesh
from mathutils import Vector
from math import sin,cos,pi
OUT=os.path.dirname(os.path.abspath(__file__));s=bpy.context.scene;random.seed(41)
COL={'car':bpy.data.collections['29 Car and bicycles'],'garage':bpy.data.collections['28 Garage and workbench']};active='car'
src=open(OUT+'/../samford-modular/build_scene.py').read();exec(src[src.index('def mesh('):src.index('timber=textured(')])
# Restore outward cube winding for newly added geometry.
cubefs=[tuple(reversed(f)) for f in cubefs]
for o in list(COL['car'].objects):
if o.name.startswith(('Car ','Family wagon','ONE FAMILY CAR')):bpy.data.objects.remove(o,do_unlink=True)
paint=mat('Tesla pearl white multi-coat',(.82,.84,.81),.23,.25);paint.node_tree.nodes['Principled BSDF'].inputs['Coat Weight'].default_value=.55
glass=mat('Tesla smoked panoramic glass',(.026,.043,.055),.14,.35)
rubber=mat('Tesla tyre rubber',(.018,.020,.021),.82)
black=mat('Tesla satin black trim',(.025,.029,.031),.4,.4)
silver=mat('Tesla machined wheel accents',(.52,.57,.59),.22,.9)
led=mat('Tesla LED white',(.8,.89,.91),.17);p=led.node_tree.nodes['Principled BSDF'];p.inputs['Emission Color'].default_value=(.8,.9,1,1);p.inputs['Emission Strength'].default_value=.8
red=mat('Tesla rear light red',(.42,.009,.012),.25)
cx,cy,GF=-4.7,-19.1,.62
def world(p):return (cx+p[0],cy+p[1],GF+p[2])
def path(name,points,r,material):
cu=bpy.data.curves.new(name,'CURVE');cu.dimensions='3D';cu.resolution_u=8;cu.bevel_depth=r;cu.bevel_resolution=2
sp=cu.splines.new('BEZIER');sp.bezier_points.add(len(points)-1)
for b,p in zip(sp.bezier_points,points):b.co=p;b.handle_left_type='AUTO';b.handle_right_type='AUTO'
ob=bpy.data.objects.new(name,cu);COL[active].objects.link(ob);cu.materials.append(material)
return ob
def apply_smooth(ob,level=1):
bm=bmesh.new();bm.from_mesh(ob.data);bmesh.ops.recalc_face_normals(bm,faces=bm.faces);bm.to_mesh(ob.data);bm.free()
bpy.context.view_layer.objects.active=ob;ob.select_set(True)
if level:
m=ob.modifiers.new('Baked flowing body surface','SUBSURF');m.levels=level;bpy.ops.object.modifier_apply(modifier=m.name)
for p in ob.data.polygons:p.use_smooth=True
ob.select_set(False)
# 2020-24 Model Y inspired body, 4.75m long, with open wheel arches.
ys=[-2.375,-2.34,-2.22,-2.02,-1.85,-1.70,-1.55,-1.445,-1.33,-1.17,-1.03,-.88,-.6,0,.55,.86,1.03,1.18,1.32,1.445,1.57,1.72,1.86,2.05,2.25,2.34,2.375]
vs=[];fs=[]
for y in ys:
w=.965-.14*(abs(y)/2.375)**6
top=.99-.23*max(0,(y-.8)/1.575)+.05*max(0,(-y-1.3)/1.075)
arch=max([.28]+[.37+math.sqrt(max(0,.405**2-(y-wy)**2)) for wy in [-1.445,1.445] if abs(y-wy)<.405])
ring=[(-w*.86,.25),(-w,arch),(-w,top-.13),(-w*.91,top),(-w*.52,top+.035),(0,top+.045),(w*.52,top+.035),(w*.91,top),(w,top-.13),(w,arch),(w*.86,.25),(0,.23)]
vs.extend(world((x,y,z)) for x,z in ring)
for j in range(len(ys)-1):
for i in range(12):a=j*12+i;b=j*12+(i+1)%12;fs.append((a,b,b+12,a+12))
fs.extend([tuple(reversed(range(12))),tuple(range((len(ys)-1)*12,len(ys)*12))])
body=mesh('TESLA MODEL Y - pearl white flowing body',vs,fs,paint);apply_smooth(body,2)
# Arched glass roof, windscreen and rear hatch form one continuous dark canopy.
sections=[(-2.04,.68,1.02,1.10),(-1.8,.73,1.02,1.28),(-1.28,.75,1.03,1.53),(-.8,.74,1.04,1.62),(-.22,.74,1.04,1.64),(.32,.74,1.03,1.60),(.75,.78,1.00,1.40),(1.12,.81,.99,1.06)]
vs=[];fs=[]
for y,w,b,t in sections:
for x,z in [(-w,b),(-w*.92,b+(t-b)*.7),(-w*.73,t-.025),(0,t),(w*.73,t-.025),(w*.92,b+(t-b)*.7),(w,b)]:vs.append(world((x,y,z)))
for j in range(len(sections)-1):
for i in range(6):a=j*7+i;fs.append((a,a+1,a+8,a+7))
canopy=mesh('Tesla sweeping panoramic glass roof and windows',vs,fs,glass);apply_smooth(canopy,2)
for side in [-1,1]:
path('Tesla continuous window surround',[world((side*w,y,b+.025)) for y,w,b,t in sections],.022,black)
path('Tesla roof shoulder pillar',[world((side*w*.79,y,t-.075)) for y,w,b,t in sections[1:-1]],.023,paint)
for y,top in [(.69,1.40),(-.32,1.59),(-1.3,1.45)]:
path('Tesla window pillar',[world((side*.81,y,1.025)),world((side*.65,y,top))],.025,black if y<.1 else paint)
for y in [-.65,.55]:box('Tesla flush black door handle',world((side*.963,y,.925)),(.016,.17,.025),black,.006)
path('Tesla door shut line',[world((side*.963,-.30,.36)),world((side*.965,-.32,.75)),world((side*.91,-.34,1.01))],.0035,black)
box('Tesla side mirror',world((side*1.02,.70,1.10)),(.24,.18,.10),black,.045)
for wy in [-1.445,1.445]:
x=side*.935
tyre=rod('Tesla 19 inch tyre',world((x-.12,wy,.36)),world((x+.12,wy,.36)),.355,rubber,sides=64)
outer=side*1.061
rod('Tesla dark aero wheel',world((outer,wy,.36)),world((outer+side*.008,wy,.36)),.248,black,sides=48)
rod('Tesla wheel centre',world((outer+side*.01,wy,.36)),world((outer+side*.019,wy,.36)),.061,silver,sides=24)
for k in range(5):
a=k*2*pi/5
path('Tesla turbine spoke',[world((outer+side*.011,wy+.07*cos(a),.36+.07*sin(a))),world((outer+side*.014,wy+.22*cos(a+.22),.36+.22*sin(a+.22)))],.022,silver)
path('Tesla narrow swept headlamp',[world((side*.47,2.30,.79)),world((side*.69,2.23,.82)),world((side*.82,2.08,.84))],.029,led)
path('Tesla red rear lamp',[world((side*.41,-2.34,1.00)),world((side*.67,-2.33,.99)),world((side*.83,-2.22,.94))],.028,red)
box('Tesla lower front air intake',world((0,2.351,.40)),(1.24,.026,.09),black,.035)
# Recognisable Tesla T emblem on the nose; no imported heavy vehicle asset.
path('Tesla T badge upper arc',[world((-.08,2.17,.828)),world((0,2.20,.824)),world((.08,2.17,.828))],.006,black)
path('Tesla T badge stem',[world((0,2.19,.826)),world((0,2.29,.812))],.006,black)
def label(name,body,pos,rot,size,material):
cu=bpy.data.curves.new(name,'FONT');cu.body=body;cu.size=size;cu.align_x='CENTER';cu.extrude=.0005
ob=bpy.data.objects.new(name,cu);COL[active].objects.link(ob);ob.location=pos;ob.rotation_euler=rot;cu.materials.append(material);return ob
box('Tesla front number plate',world((0,2.382,.60)),(.41,.018,.105),paint,.012)
label('Tesla MODEL Y plate','MODEL Y',world((0,2.394,.573)),(pi/2,0,pi),.061,black)
label('Tesla rear lettering','T E S L A',world((0,-2.38,.92)),(pi/2,0,0),.078,silver)
active='garage'
wood=bpy.data.materials['Unpainted hardwood - long X grain'];steel=bpy.data.materials['Reusable dark steel bolts and shoes']
solar=mat('Garage solar blue black cells',(.023,.065,.098),.23,.65);green=mat('Solar charge indicator',(.1,.65,.3),.3)
green.node_tree.nodes['Principled BSDF'].inputs['Emission Color'].default_value=(.1,1,.3,1);green.node_tree.nodes['Principled BSDF'].inputs['Emission Strength'].default_value=1.3
box('SOLAR CHARGING - timber backboard',(-6.88,-20.8,GF+1.45),(.07,.52,.89),wood,.025)
box('Tesla wall connector',(-6.79,-20.8,GF+1.45),(.16,.24,.47),paint,.075)
box('Tesla charger black face',(-6.70,-20.8,GF+1.46),(.016,.20,.37),black,.035)
box('Solar charging green status',(-6.685,-20.8,GF+1.62),(.009,.13,.014),green,.005)
path('EV cable to left rear charge port',[(-6.73,-20.8,GF+1.20),(-6.59,-20.8,GF+.60),(-6.34,-20.8,GF+.20),(-5.96,-20.92,GF+.24),(-5.71,-21.08,GF+.76),(-5.66,-21.08,GF+.93)],.014,black)
box('Tesla plugged charge connector',(-5.67,-21.08,GF+.94),(.09,.07,.07),black,.022)
for x in [-5.9,-4.0,-2.1,-.2]:
for y in [-20.9,-18.5]:
z=GF+3.22+.05*(y+19)+.105
for name,dims,offset,material in [('Garage solar aluminium frame',(1.78,2.14,.05),0,silver),('Garage photovoltaic module',(1.70,2.06,.018),.034,solar)]:
ob=box(name,(x,y,z+offset),dims,material,.009);ob.rotation_euler.x=math.atan(.05)
for i in range(1,6):
ob=box('Solar cell division',(x-.85+i*1.7/6,y,z+.046),(.005,2.02,.004),silver);ob.rotation_euler.x=math.atan(.05)
for j in range(1,10):
yy=y-1.03+j*2.06/10;box('Solar cell row',(x,yy,z+.046+.05*(yy-y)),(1.69,.005,.004),silver)
# A planted trellis softens the workshop's east wall without narrowing car access.
soil=bpy.data.materials['Damp brown garden soil'];leaves=[bpy.data.materials['Leaf '+str(i)] for i in range(5)]
for x in [1.23,2.03]:box('Garage edible planter timber side',(x,-19,.79),(.055,3.6,.43),wood,.015)
for y in [-20.8,-17.2]:box('Garage edible planter end',(1.63,y,.79),(.85,.055,.43),wood,.015)
box('Garage edible planter soil',(1.63,-19,.94),(.75,3.5,.06),soil)
for y in [-20.7,-19.8,-18.9,-18,-17.3]:box('Garage climbing garden upright',(1.14,y,1.88),(.04,.04,2.35),wood)
for z in [1.2,1.65,2.10,2.55,2.95]:box('Garage climbing garden cross rail',(1.14,-19,z),(.04,3.6,.035),wood)
lv=[];lf=[];lm=[]
for i in range(240):
y=random.uniform(-20.7,-17.3);z=random.uniform(1.03,2.88);x=1.17+random.uniform(-.05,.20)
if random.random()<.40:z=random.uniform(.96,1.32);x=random.uniform(1.24,2.0)
a=random.uniform(0,2*pi);length=random.uniform(.07,.14);c=Vector((x,y,z));long=Vector((.1,cos(a),sin(a)))*length;wide=Vector((.3,-sin(a),cos(a)))*length*.48;j=len(lv)
lv.extend(tuple(p) for p in [c-long,c+wide,c+long,c-wide]);lf.append((j,j+1,j+2,j+3));lm.append(random.randrange(5))
o=mesh('Garage climbing edible greenery',lv,lf)
for m in leaves:o.data.materials.append(m)
for p,i in zip(o.data.polygons,lm):p.material_index=i
# Bake added curve and text geometry so the fast tour includes every feature.
for col in COL.values():
for o in list(col.objects):
if o.type in {'CURVE','FONT'}:
bpy.ops.object.select_all(action='DESELECT');o.select_set(True);bpy.context.view_layer.objects.active=o;bpy.ops.object.convert(target='MESH');o.select_set(False)
terrain=bpy.data.objects['Continuous gently north falling hillside']
for v in terrain.data.vertices:
p=terrain.matrix_world@v.co
if -7.2<p.x<1.2 and -22.7<p.y<-15.3 and p.z>GF-.23:
p.z=GF-.23;v.co=terrain.matrix_world.inverted()@p
terrain.data.update()
cam=bpy.data.objects['PLAY TOUR camera']
for frame,pos,target in [(2401,(-6.5,-12.8,3.00),(-3.7,-19.6,1.45)),(2520,(-6.25,-14.45,2.50),(-3.8,-19.9,1.44))]:
s.frame_set(frame);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);cam.data.lens=26;cam.data.keyframe_insert('lens',frame=frame)
for o in bpy.data.collections['22 PLAY - guided camera tour'].objects:
if o.type=='FONT' and '21 /' in o.data.body:o.data.body='21 / TESLA, SOLAR CHARGING & WORKSHOP'
s['Design direction']='Solarpunk homestead: natural timber and clay, food growing, solar-powered electric mobility, rainwater capture, repair workshop and adaptable modular rooms.'
s['Tesla reference']='Simplified 2020-2024 Model Y inspired concept model, approximately 4.75 m long. https://www.tesla.com/ownersmanual/2020_2024_modely/en_cn/Owners_Manual.pdf'
s['Energy concept']='Eight extra garage solar modules and conceptual EV wall connector. No electrical capacity or charging performance is simulated.'
s.frame_set(1);s.camera=cam
bpy.ops.wm.save_as_mainfile(filepath=OUT+'/samford-solarpunk-detailed.blend')
print('SOLARPUNK_TESLA_BUILT',flush=True)