Preserve homestead context and garden-centred Blender design
This commit is contained in:
139
source/samford-retreat-garage/solarpunk_tesla.py
Normal file
139
source/samford-retreat-garage/solarpunk_tesla.py
Normal 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)
|
||||
Reference in New Issue
Block a user