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 -35d 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