46 lines
3.4 KiB
Python
46 lines
3.4 KiB
Python
import bpy,bmesh,math,random,os
|
|
from mathutils import Vector
|
|
random.seed(523)
|
|
OUT=os.path.dirname(os.path.abspath(__file__))
|
|
s=bpy.context.scene
|
|
# Ensure all closed construction solids and the atmosphere have outward-facing surfaces.
|
|
fixed=0
|
|
for o in bpy.data.objects:
|
|
if o.type=='MESH' and len(o.data.vertices)==8 and len(o.data.polygons)==6:
|
|
bm=bmesh.new(); bm.from_mesh(o.data); bmesh.ops.recalc_face_normals(bm,faces=list(bm.faces)); bm.to_mesh(o.data); bm.free(); fixed+=1
|
|
print('SOLID_NORMALS_CHECKED',fixed,flush=True)
|
|
def ground(x,y): return -.024*y+.004*x+.12*math.sin(x*.19)*math.cos(y*.14)+.055*math.sin(x*.72+y*.32)
|
|
# Put both tanks on the north-west side of the sleeping module, visible from the main camera.
|
|
prefixes=('2000 mm dark green poly rainwater tank','Poly tank','Tank service lid','Accessible tank outlet','Gravel tank pad')
|
|
for o in bpy.data.objects:
|
|
if not o.name.startswith(prefixes): continue
|
|
center=o.matrix_world@(sum((Vector(v) for v in o.bound_box),Vector())/8)
|
|
shift=Vector((0,4.1,0)) if center.y>-9 else Vector((-2.3,6.4,0))
|
|
shift.z=ground(center.x+shift.x,center.y+shift.y)-ground(center.x,center.y)
|
|
o.location+=shift
|
|
# Loose gravel fragments soften the hard geometry of unsealed paths.
|
|
vs=[]; fs=[]
|
|
routes=[([(1,14),(0,9),(0,7),(-3,6.2),(-7,5),(-9,1),(-10,-3),(-13,-4.6)],1.5), ([(0,7),(4,7.6),(8,8.4),(14,8),(19,7),(20,2)],1.1), ([(20,2),(20,-4.6),(18.2,-5.7)],1)]
|
|
for points,width in routes:
|
|
for a,b in zip(points,points[1:]):
|
|
a,b=Vector(a),Vector(b); delta=b-a; side=Vector((-delta.y,delta.x)).normalized()
|
|
for _ in range(int(delta.length*135)):
|
|
p=a+delta*random.random()+side*random.uniform(-width*.62,width*.62); x,y=p; z=ground(x,y)+.04; r=random.uniform(.013,.04); k=len(vs)
|
|
vs.extend([(x-r,y-r,z),(x+r,y-r*.5,z),(x+r*.7,y+r,z),(x-r*.7,y+r*.7,z),(x,y,z+r*.8)])
|
|
fs.extend([(k,k+1,k+4),(k+1,k+2,k+4),(k+2,k+3,k+4),(k+3,k,k+4)])
|
|
me=bpy.data.meshes.new('Loose gravel stones'); me.from_pydata(vs,[],fs); me.materials.append(bpy.data.materials['Loose local gravel - fine stones']); ob=bpy.data.objects.new('Scattered loose gravel and irregular path edges',me); bpy.data.collections['07 Terrain and paths'].objects.link(ob)
|
|
# Modest golden light; a low-density atmosphere scatters it through the canopy.
|
|
s.world.node_tree.nodes['Background'].inputs['Strength'].default_value=.6
|
|
s.view_settings.exposure=.3
|
|
sun=bpy.data.objects['Warm early morning sun from north-east']; sun.data.energy=3.0
|
|
v=next(n for n in bpy.data.materials['Very light canopy haze'].node_tree.nodes if n.type=='PRINCIPLED_VOLUME'); v.inputs['Density'].default_value=.0012
|
|
for area in [a for screen in bpy.data.screens for a in screen.areas if a.type=='VIEW_3D']:
|
|
area.spaces.active.shading.type='SOLID'; area.spaces.active.shading.color_type='MATERIAL'; area.spaces.active.region_3d.view_perspective='CAMERA'
|
|
bpy.data.objects['Subtle atmosphere over hillside'].hide_set(True)
|
|
# hide_set affects only the interactive viewport; the atmosphere remains enabled for render.
|
|
s.render.resolution_percentage=100; s.cycles.samples=96; s.render.filepath=os.path.join(OUT,'samford-modular-final.png')
|
|
bpy.ops.wm.save_as_mainfile(filepath=os.path.join(OUT,'samford-modular-home.blend'))
|
|
s.cycles.device='CPU'; s.cycles.samples=12; s.render.resolution_percentage=40; s.render.filepath=os.path.join(OUT,'samford-modular-light-check.png')
|
|
bpy.ops.render.render(write_still=True)
|
|
print('LIGHTING_CHECK_COMPLETE',flush=True)
|