Add property wind turbine concept and update project gallery
This commit is contained in:
192
source/samford-v009-wind/add_wind_turbine.py
Normal file
192
source/samford-v009-wind/add_wind_turbine.py
Normal file
@@ -0,0 +1,192 @@
|
||||
"""Run with Blender --background INPUT.blend --python this.py -- --output OUTPUT.blend.
|
||||
|
||||
Add an illustrative turbine to either public v007 or private v008. Never publish
|
||||
an output based on the licensed v008 scene. Dimensions are visual placeholders.
|
||||
"""
|
||||
import argparse
|
||||
import bpy
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from mathutils import Vector
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--output', required=True)
|
||||
parser.add_argument('--render')
|
||||
args = parser.parse_args(sys.argv[sys.argv.index('--') + 1:])
|
||||
output = Path(args.output).resolve()
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
s = bpy.context.scene
|
||||
assert '39 WIND - property turbine concept' not in bpy.data.collections, 'Already added'
|
||||
detail = bpy.data.collections['DETAIL - full geometry for renders']
|
||||
fast = bpy.data.collections['00 FAST TOUR - lightweight viewport']
|
||||
detail.hide_viewport = False
|
||||
fast.hide_viewport = True
|
||||
coll = bpy.data.collections.new('39 WIND - property turbine concept')
|
||||
detail.children.link(coll)
|
||||
|
||||
def material(name, color, metallic=0):
|
||||
m = bpy.data.materials.new(name)
|
||||
m.diffuse_color = (*color, 1)
|
||||
m.use_nodes = True
|
||||
shader = m.node_tree.nodes.get('Principled BSDF')
|
||||
shader.inputs['Base Color'].default_value = (*color, 1)
|
||||
shader.inputs['Metallic'].default_value = metallic
|
||||
shader.inputs['Roughness'].default_value = .38
|
||||
return m
|
||||
|
||||
white = material('WIND - warm ivory composite', (.79, .80, .74))
|
||||
steel = material('WIND - galvanised mast', (.42, .47, .45), .65)
|
||||
dark = material('WIND - dark service enclosure', (.095, .14, .12), .35)
|
||||
concrete = material('WIND - foundation concept', (.37, .35, .29))
|
||||
|
||||
def adopt(ob, name, mat):
|
||||
ob.name = 'WIND - ' + name
|
||||
for c in list(ob.users_collection):
|
||||
c.objects.unlink(ob)
|
||||
coll.objects.link(ob)
|
||||
ob.data.materials.append(mat)
|
||||
return ob
|
||||
|
||||
def box(name, p, dimensions, mat):
|
||||
bpy.ops.mesh.primitive_cube_add(size=1, location=p)
|
||||
ob = adopt(bpy.context.object, name, mat)
|
||||
ob.scale = dimensions
|
||||
return ob
|
||||
|
||||
def cone(name, a, b, r1, r2, mat):
|
||||
a, b = Vector(a), Vector(b)
|
||||
bpy.ops.mesh.primitive_cone_add(vertices=40, radius1=r1, radius2=r2,
|
||||
depth=(b-a).length, location=(a+b)/2)
|
||||
ob = adopt(bpy.context.object, name, mat)
|
||||
ob.rotation_euler = (b-a).to_track_quat('Z', 'Y').to_euler()
|
||||
for face in ob.data.polygons:
|
||||
face.use_smooth = True
|
||||
return ob
|
||||
|
||||
x, y = -36., 40.
|
||||
ground = lambda x, y: -.024*y + .004*x + .12*math.sin(x*.19)*math.cos(y*.14) + .055*math.sin(x*.72+y*.32)
|
||||
z = ground(x, y)
|
||||
hub = z + 18.
|
||||
box('foundation cap - indicative only', (x, y, z+.10), (2.1, 2.1, .30), concrete)
|
||||
cone('tapered freestanding mast', (x,y,z+.25), (x,y,hub), .27, .13, steel)
|
||||
cone('base flange', (x,y,z+.23), (x,y,z+.30), .42, .42, steel)
|
||||
for i in range(8):
|
||||
angle = i*math.tau/8
|
||||
bx, by = x+.34*math.cos(angle), y+.34*math.sin(angle)
|
||||
cone('anchor bolt %02d' % i, (bx,by,z+.3), (bx,by,z+.37), .025, .025, dark)
|
||||
cone('nacelle', (x,y-.35,hub), (x,y+.7,hub), .24, .18, white)
|
||||
cone('rotor hub', (x,y-.32,hub), (x,y-.73,hub), .23, .07, white)
|
||||
cone('tail boom', (x,y+.45,hub), (x,y+2.0,hub), .055, .035, steel)
|
||||
box('tail vane', (x,y+1.9,hub+.32), (.055,.8,.65), white)
|
||||
|
||||
# Three tapered swept blades, with closed airfoil-like cross sections.
|
||||
for blade in range(3):
|
||||
angle = blade*math.tau/3
|
||||
vs, faces = [], []
|
||||
stations = [(.19,.10,0),(.55,.25,0),(1.1,.23,.04),(2.,.16,.13),(2.8,.08,.22),(3.,.025,.25)]
|
||||
for r, halfwidth, sweep in stations:
|
||||
for tangent, depth in [(-halfwidth,0),(0,-.055),(halfwidth,0),(0,.045)]:
|
||||
t = tangent+sweep
|
||||
vs.append((x+t*math.cos(angle)+r*math.sin(angle), y-.48+depth,
|
||||
hub-t*math.sin(angle)+r*math.cos(angle)))
|
||||
for j in range(len(stations)-1):
|
||||
for k in range(4):
|
||||
faces.append((4*j+k,4*j+(k+1)%4,4*(j+1)+(k+1)%4,4*(j+1)+k))
|
||||
faces.extend([(3,2,1,0),tuple(range(len(vs)-4,len(vs)))])
|
||||
mesh = bpy.data.meshes.new('WIND blade mesh')
|
||||
mesh.from_pydata(vs, [], faces)
|
||||
mesh.update()
|
||||
ob = bpy.data.objects.new('WIND - rotor blade %d' % (blade+1), mesh)
|
||||
coll.objects.link(ob)
|
||||
mesh.materials.append(white)
|
||||
for face in mesh.polygons:
|
||||
face.use_smooth = True
|
||||
box('lockable isolation cabinet - indicative', (x+.72,y,z+.92), (.45,.3,.65), dark)
|
||||
cone('service conduit', (x+.72,y,z+.1), (x+.72,y,z+.63), .045,.045,steel)
|
||||
|
||||
# Exact low-poly display copies keep the lightweight viewport aligned.
|
||||
bpy.context.view_layer.update()
|
||||
for ob in list(coll.objects):
|
||||
proxy = ob.copy()
|
||||
proxy.data = ob.data.copy()
|
||||
proxy.name = 'FAST | ' + ob.name
|
||||
fast.objects.link(proxy)
|
||||
proxy.hide_render = True
|
||||
|
||||
tour = bpy.data.collections['22 PLAY - guided camera tour']
|
||||
cam = bpy.data.objects['PLAY TOUR camera']
|
||||
title = '32 / WIND POWER - PROPERTY CONCEPT'
|
||||
for frame, position in [(3721,(-57,66,25)),(3840,(-54,62,24))]:
|
||||
s.frame_set(frame)
|
||||
cam.location = position
|
||||
cam.rotation_euler = (Vector((x,y,hub*.57))-cam.location).to_track_quat('-Z','Y').to_euler()
|
||||
cam.keyframe_insert('location', frame=frame)
|
||||
cam.keyframe_insert('rotation_euler', frame=frame)
|
||||
cam.data.lens = 38
|
||||
cam.data.keyframe_insert('lens', frame=frame)
|
||||
ref = next(o for o in tour.objects if o.type=='FONT' and o.data.body.startswith('31 /'))
|
||||
ref.scale = (0,0,0)
|
||||
ref.keyframe_insert('scale', frame=3721)
|
||||
caption = ref.copy()
|
||||
caption.data = ref.data.copy()
|
||||
caption.animation_data_clear()
|
||||
tour.objects.link(caption)
|
||||
caption.name = 'Tour caption 32 - wind turbine concept'
|
||||
caption.data.body = title
|
||||
caption.location = (-36/38*.91,36/38*9/16*.84,-2)
|
||||
for frame, value in [(1,0),(3720,0),(3721,1),(3840,1)]:
|
||||
caption.scale = (value,value,value)
|
||||
caption.keyframe_insert('scale', frame=frame)
|
||||
for ob in (ref,caption):
|
||||
for layer in ob.animation_data.action.layers:
|
||||
for strip in layer.strips:
|
||||
for bag in strip.channelbags:
|
||||
for curve in bag.fcurves:
|
||||
for key in curve.keyframe_points:
|
||||
key.interpolation = 'CONSTANT'
|
||||
s.frame_end = 3840
|
||||
s.timeline_markers.new(title, frame=3721)
|
||||
s['Wind turbine concept'] = 'v009: three-blade freestanding turbine at (-36,40); 18 m hub / 6 m rotor are visual placeholders. No measured wind resource, selected product, yield, engineering or approvals.'
|
||||
s['PLAY THE TOUR'] = 'Hover over the 3D view and press Space. 32 chapters, 2 min 40 sec. Shift+Left Arrow restarts. Wind turbine: chapter 32.'
|
||||
text = bpy.data.texts.get('START HERE - PRESS PLAY')
|
||||
if text:
|
||||
text.write('\n\n'+s['PLAY THE TOUR']+'\n'+s['Wind turbine concept'])
|
||||
|
||||
# A separate overview camera leaves all existing inspection chapters intact.
|
||||
camera_data = bpy.data.cameras.new('Wind property overview')
|
||||
overview = bpy.data.objects.new('Wind property overview', camera_data)
|
||||
s.collection.objects.link(overview)
|
||||
overview.location = (-85,90,60)
|
||||
overview.rotation_euler = (Vector((-5,5,4))-overview.location).to_track_quat('-Z','Y').to_euler()
|
||||
camera_data.type = 'ORTHO'
|
||||
camera_data.ortho_scale = 108
|
||||
camera_data.clip_end = 1000
|
||||
s.frame_set(3721)
|
||||
s.camera = cam
|
||||
detail.hide_viewport = True
|
||||
fast.hide_viewport = False
|
||||
s.render.resolution_x, s.render.resolution_y = 2560, 1440
|
||||
s.render.resolution_percentage = 100
|
||||
bpy.ops.wm.save_as_mainfile(filepath=str(output))
|
||||
if args.render:
|
||||
s.camera = overview
|
||||
for ob in tour.objects:
|
||||
if ob.type == 'FONT':
|
||||
ob.hide_render = True
|
||||
detail.hide_viewport = False
|
||||
fast.hide_viewport = True
|
||||
s.render.engine = 'CYCLES'
|
||||
s.render.resolution_percentage = 60
|
||||
s.cycles.samples = 32
|
||||
s.cycles.use_denoising = True
|
||||
prefs = bpy.context.preferences.addons['cycles'].preferences
|
||||
prefs.compute_device_type = 'METAL'
|
||||
prefs.get_devices()
|
||||
for device in prefs.devices:
|
||||
device.use = device.type == 'METAL'
|
||||
s.cycles.device = 'GPU'
|
||||
s.render.filepath = str(Path(args.render).resolve())
|
||||
bpy.ops.render.render(write_still=True)
|
||||
print('WIND_REVISION_SAVED', output, flush=True)
|
||||
Reference in New Issue
Block a user