71 lines
6.1 KiB
Python
71 lines
6.1 KiB
Python
import bpy, os, math, bmesh, json
|
|
from mathutils import Vector
|
|
OUT=os.path.dirname(os.path.abspath(__file__)); s=bpy.context.scene
|
|
c=bpy.data.collections['20 Spacious site connections']
|
|
clay=bpy.data.materials['Soft warm clay plaster - natural mineral texture']; wood=bpy.data.materials['Unpainted hardwood - long X grain']
|
|
def box(n,p,dims,mat):
|
|
bpy.ops.mesh.primitive_cube_add(size=1,location=p); o=bpy.context.object; o.name=n; o.dimensions=dims; bpy.ops.object.transform_apply(location=False,rotation=False,scale=True)
|
|
for col in list(o.users_collection): col.objects.unlink(o)
|
|
c.objects.link(o); o.data.materials.append(mat); return o
|
|
F=1.12
|
|
# Explicit wall segments preserve a real, unobstructed doorway without boolean ambiguity.
|
|
o=bpy.data.objects.get('Insulated end wall')
|
|
if o: bpy.data.objects.remove(o,do_unlink=True)
|
|
for ya,yb,zlo in [(-3,-2.365,F),(-1.235,3,F),(-2.365,-1.235,F+2.34)]:
|
|
def top(y): return F+2.7+(y+3)*.7/6
|
|
vs=[(x,y,z) for x in [-6.09,-5.91] for y,z in [(ya,zlo),(yb,zlo),(yb,top(yb)),(ya,top(ya))]]
|
|
me=bpy.data.meshes.new('Living west wall with clear doorway'); me.from_pydata(vs,[],[(0,3,2,1),(4,5,6,7),(0,1,5,4),(1,2,6,5),(2,3,7,6),(3,0,4,7)]); me.materials.append(clay); ob=bpy.data.objects.new('Living west wall | clear corridor opening',me); c.objects.link(ob)
|
|
o=bpy.data.objects.get('Short corridor north clay wall')
|
|
if o: bpy.data.objects.remove(o,do_unlink=True)
|
|
box('Corridor north wall below high window',(-7.75,-1.16,F+.895),(3.5,.12,1.79),clay)
|
|
box('Corridor north wall above high window',(-7.75,-1.16,F+2.425),(3.5,.12,.15),clay)
|
|
for x in [-9.31,-6.19]: box('Corridor north wall window end',(x,-1.16,F+2.07),(.38,.12,.56),clay)
|
|
o=bpy.data.objects['Bedroom privacy partition south']; o.location.y=-5.61; o.dimensions.y=3.58
|
|
o=bpy.data.objects['Bedroom privacy partition north return']; o.location.y=-2.57; o.dimensions.y=.34
|
|
for x in [-9.2,-8.22]: box('Bathroom solid door jamb',(x,-3.85,F+1.14),(.05,.18,2.28),wood)
|
|
# Close the sloping wall/roof junctions on each future child's enclosed room.
|
|
for cx,cy in [(-11,12),(-3,17),(7,15)]:
|
|
for xx in [cx-2.25,cx+2.25]:
|
|
ya,yb=cy-2.4,cy+2.4
|
|
vs=[(x,y,z) for x in [xx-.085,xx+.085] for y,z in [(ya,F+2.60),(yb,F+2.60),(yb,F+3.22),(ya,F+2.87)]]
|
|
me=bpy.data.meshes.new('Sloping sealed child wall top'); me.from_pydata(vs,[],[(0,3,2,1),(4,5,6,7),(0,1,5,4),(1,2,6,5),(2,3,7,6),(3,0,4,7)]); me.materials.append(clay); ob=bpy.data.objects.new('Child room sealed sloping wall top',me); c.objects.link(ob)
|
|
box('Child room north wall roof junction',(cx,cy+2.4,F+3.10),(4.5,.17,.22),clay)
|
|
box('Child room south wall roof junction',(cx,cy-2.4,F+2.78),(4.5,.17,.17),clay)
|
|
for ob in s.objects:
|
|
if ob.type=='MESH' and len(ob.data.vertices)==8 and len(ob.data.polygons)==6:
|
|
bm=bmesh.new(); bm.from_mesh(ob.data); bmesh.ops.recalc_face_normals(bm,faces=list(bm.faces)); bm.to_mesh(ob.data); bm.free()
|
|
if ob.type=='MESH' and ob.name.startswith(('Eucalyptus individual canopy leaves','Mango / avocado individual canopy leaves')): ob.hide_set(False)
|
|
cam=s.camera; data=cam.data
|
|
for f,pos,target in [(601,(-6.6,-1.8,2.70),(-8.9,-2.05,2.13)),(720,(-7.7,-1.8,2.65),(-8.9,-2.7,2.10))]:
|
|
cam.location=pos; cam.rotation_euler=(Vector(target)-cam.location).to_track_quat('-Z','Y').to_euler(); cam.keyframe_insert('location',frame=f); cam.keyframe_insert('rotation_euler',frame=f)
|
|
# Update saved viewport state for Play and retain the leafy canopy in the interactive scene.
|
|
for screen in bpy.data.screens:
|
|
for area in screen.areas:
|
|
if area.type=='VIEW_3D':
|
|
area.spaces.active.region_3d.view_perspective='CAMERA'; area.spaces.active.shading.type='SOLID'; area.spaces.active.shading.color_type='MATERIAL'; area.spaces.active.overlay.show_overlays=False
|
|
s['Viewport performance']='Material-colour camera tour. Dense grass is hidden only in the viewport; canopy remains visible. Glazing uses outlines in solid viewport and renders as glass in Cycles.'
|
|
# Check the doorway's centre ray reaches the corridor interior, not a wall at its threshold.
|
|
dg=bpy.context.evaluated_depsgraph_get(); start=Vector((-5.5,-1.8,2.3)); hit=s.ray_cast(dg,start,Vector((-1,0,0)),distance=4.2)
|
|
assert not hit[0] or hit[1].x<-7.9, (hit[4].name,hit[1][:])
|
|
report=json.load(open(os.path.join(OUT,'tour-checks.json'))); report['doorway_clear']=True
|
|
positions=[]
|
|
for ch in json.load(open(os.path.join(OUT,'tour-chapters.json'))):
|
|
s.frame_set(ch['start']); a=list(cam.location); s.frame_set(ch['end']); b=list(cam.location); assert (Vector(a)-Vector(b)).length>.01; positions.append({'chapter':ch['title'],'start_camera':a,'end_camera':b})
|
|
report['all_chapters_have_camera_motion']=True; report['render_resolution']=[2560,1440]; json.dump(report,open(os.path.join(OUT,'tour-checks.json'),'w'),indent=2)
|
|
s.frame_set(1); s.render.engine='CYCLES'; s.render.resolution_percentage=100; s.cycles.samples=40; s.cycles.use_denoising=True; s.cycles.adaptive_threshold=.045
|
|
prefs=bpy.context.preferences.addons['cycles'].preferences; prefs.compute_device_type='METAL'; prefs.get_devices()
|
|
for dev in prefs.devices: dev.use=dev.type=='METAL'
|
|
s.cycles.device='GPU'; s.render.filepath=os.path.join(OUT,'family-homestead-overview.png')
|
|
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'samford-spacious-guided-tour.blend'))
|
|
bpy.ops.render.render(write_still=True)
|
|
print('FAMILY_OVERVIEW_RENDERED',flush=True)
|
|
s.frame_set(2460); s.render.filepath=os.path.join(OUT,'three-childrens-rooms.png'); bpy.ops.render.render(write_still=True)
|
|
print('CHILDREN_COURTYARD_RENDERED',flush=True)
|
|
s.frame_set(2160); s.render.filepath=os.path.join(OUT,'child-room-interior.png'); bpy.ops.render.render(write_still=True)
|
|
print('CHILD_ROOM_INTERIOR_RENDERED',flush=True)
|
|
# One last fast screenshot verifies the revised short-corridor chapter.
|
|
for ob in s.objects:
|
|
if ob.name=='Subtle atmosphere over hillside': ob.hide_render=True
|
|
s.frame_set(660); s.render.engine='BLENDER_WORKBENCH'; s.render.resolution_percentage=30; s.display.shading.color_type='MATERIAL'; s.render.filepath=os.path.join(OUT,'corridor-check.png'); bpy.ops.render.render(write_still=True)
|
|
print('FINAL_TOUR_VERIFIED',flush=True)
|