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