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)