"""Preview renders of the Spearfish: studio-in-water look (dark blue world, sun from above, rim lights). Not part of the export: camera, lights and world live in the collection "_preview" (hidden from the export). Run in the open Blender: exec with g["VIEWS"] = [...] and g["ENGINE"] = 'BLENDER_EEVEE' / 'CYCLES'.""" import bpy, math, os for ob in bpy.data.objects: # the rig's drivers can come back flagged invalid if ob.animation_data: for fc in ob.animation_data.drivers: fc.driver.expression = fc.driver.expression fc.driver.is_valid = True from mathutils import Vector, Euler OUT = r"D:\HADAL - The Descent\models\tender-spearfish\previews" VIEWS = globals().get("VIEWS", ["hero", "ref", "side", "top", "front", "stern", "under", "cabin", "clips", "seat_L", "seat_R", "frame_nose", "frame_q", "frame_aft"]) ENGINE = globals().get("ENGINE", "CYCLES") TAG = globals().get("TAG", "v06") RES = globals().get("RES", (1400, 900)) SAMPLES = globals().get("SAMPLES", 96) sc = bpy.context.scene pc = bpy.data.collections.get("_preview") if pc is None: pc = bpy.data.collections.new("_preview"); sc.collection.children.link(pc) def obj(name, data=None): o = bpy.data.objects.get(name) if o is None: o = bpy.data.objects.new(name, data); pc.objects.link(o) return o cam = obj("_PrevCam", bpy.data.cameras.get("_PrevCam") or bpy.data.cameras.new("_PrevCam")) sc.camera = cam cam.data.clip_start = 0.02 # the seat views start inside the cabin def light(name, kind, energy, color, rot, loc=(0, 0, 0), size=1.0): ld = bpy.data.lights.get(name) or bpy.data.lights.new(name, kind) ld.energy = energy; ld.color = color if kind == 'SUN': ld.angle = math.radians(6) else: ld.shape = 'DISK'; ld.size = size o = obj(name, ld); o.rotation_euler = Euler(rot); o.location = loc light("_PrevSun", 'SUN', 3.2, (0.80, 0.92, 1.0), (math.radians(20), math.radians(-12), 0)) light("_PrevRim", 'AREA', 900, (0.55, 0.75, 1.0), (math.radians(-65), 0, math.radians(200)), loc=(-4, 8, 5), size=6) light("_PrevFill", 'AREA', 350, (0.70, 0.85, 1.0), (math.radians(70), 0, math.radians(-30)), loc=(6, -6, -2), size=6) w = bpy.data.worlds.get("_PrevWorld") or bpy.data.worlds.new("_PrevWorld") w.use_nodes = True bg = w.node_tree.nodes.get("Background") bg.inputs[1].default_value = 1.0 # a gradient sea (lighter above, dark below), so glass and the clear coat have something to reflect: under a uniform world # the smoked glass mirrors the same colour and vanishes nt = w.node_tree for n in [n for n in nt.nodes if n.name.startswith("_g")]: nt.nodes.remove(n) tc = nt.nodes.new("ShaderNodeTexCoord"); tc.name = "_g_tc" sx = nt.nodes.new("ShaderNodeSeparateXYZ"); sx.name = "_g_sep" cr = nt.nodes.new("ShaderNodeValToRGB"); cr.name = "_g_ramp" nt.links.new(tc.outputs["Generated"], sx.inputs[0]) mr = nt.nodes.new("ShaderNodeMapRange"); mr.name = "_g_map" mr.inputs["From Min"].default_value = -1.0; mr.inputs["From Max"].default_value = 1.0 nt.links.new(sx.outputs["Z"], mr.inputs["Value"]) nt.links.new(mr.outputs["Result"], cr.inputs["Fac"]) e = cr.color_ramp.elements e[0].position = 0.30; e[0].color = (0.004, 0.012, 0.022, 1) e[1].position = 0.95; e[1].color = (0.20, 0.36, 0.48, 1) mid = e.new(0.55); mid.color = (0.016, 0.055, 0.090, 1) nt.links.new(cr.outputs["Color"], bg.inputs[0]) sc.world = w sc.render.engine = ENGINE sc.render.resolution_x, sc.render.resolution_y = RES sc.render.resolution_percentage = 100 sc.view_settings.view_transform = 'AgX' sc.view_settings.look = 'AgX - Medium High Contrast' if 'AgX - Medium High Contrast' in [i.identifier for i in bpy.types.ColorManagedViewSettings.bl_rna.properties['look'].enum_items] else 'None' if ENGINE == 'CYCLES': sc.cycles.samples = SAMPLES sc.cycles.use_denoising = True prefs = bpy.context.preferences.addons['cycles'].preferences sc.cycles.device = 'CPU' for kind in ('OPTIX', 'CUDA', 'HIP', 'ONEAPI'): # this PC bakes and renders on HIP try: prefs.compute_device_type = kind except Exception: continue prefs.refresh_devices() if any(d.type == kind for d in prefs.devices): for d in prefs.devices: d.use = d.type == kind sc.cycles.device = 'GPU' break for c in ("SPEARFISH | Collision (UCX)", "SPEARFISH | Sockets"): if bpy.data.collections.get(c): bpy.data.collections[c].hide_render = True # views: (location, look-at, lens) V = { "hero": ((7.8, -9.4, 4.4), (0.0, -0.5, 0.1), 50), "ref": ((-7.4, -9.6, 5.8), (0.0, -0.3, 0.1), 50), "side": ((13.5, -0.1, 0.25), (0.0, -0.1, 0.25), 50), "top": ((0.0, -0.1, 14.5), (0.0, -0.1, 0.0), 45), "front": ((1.4, -11.5, 0.2), (0.0, -2.5, 0.1), 50), "stern": ((-5.8, 8.8, 2.8), (0.0, 1.6, 0.3), 50), "cabin": ((2.4, -4.6, -0.2), (0.0, -2.4, -0.1), 35), "under": ((4.5, -6.5, -5.5), (0.0, -0.2, -0.3), 50), "clips": ((2.0, -2.4, 3.2), (0.0, -0.09, 1.1), 50), "frame_nose": ((0.0, -6.6, -0.12), (0.0, -3.6, -0.30), 75), # the nose's underside, where the frames meet the keel "frame_q": ((2.7, -4.8, 0.45), (0.5, -2.4, 0.05), 30), # the canopy frame from ahead and above "frame_aft": ((2.4, 0.4, 0.5), (0.95, -0.8, 0.1), 38), # the canopy's aft end and the canard behind it } for nm_, sock in (("seat_L", "SOCKET_Seat_L_Eye"), ("seat_R", "SOCKET_Seat_R_Eye")): # a look from each seat's eye if sock in bpy.data.objects: e_ = bpy.data.objects[sock].matrix_world.translation.copy() V[nm_] = (tuple(e_), tuple(e_ + Vector((0, -1, 0))), 16) for name in VIEWS: loc, at, lens = V[name] cam.location = loc d = Vector(at) - Vector(loc) cam.rotation_euler = d.to_track_quat('-Z', 'Y').to_euler() cam.data.lens = lens if name == "top": cam.rotation_euler = Euler((0, 0, 0)) sc.render.filepath = os.path.join(OUT, "%s_%s.png" % (TAG, name)) bpy.ops.render.render(write_still=True)