"""Preview renders of the Mako: 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-mako\previews" VIEWS = globals().get("VIEWS", ["hero", "ref", "side", "top", "front", "stern", "under", "cockpit", "clips", "pilot"]) ENGINE = globals().get("ENGINE", "CYCLES") TAG = globals().get("TAG", "v01") 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) # the cockpit with its lights on (a preview light, not part of the asset): a dim warm-white lamp under the hood light("_PrevCockpit", 'AREA', 14, (1.0, 0.95, 0.88), (math.radians(180), 0, 0), loc=(0, -0.68, 0.62), size=0.5) # v04: over the seat (the origin moved 20 px aft) _cl = bpy.data.objects["_PrevCockpit"] _cl.visible_camera = False # light the cockpit, but never show the lamp itself _cl.visible_glossy = False _cl.visible_transmission = False 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 ("MAKO | Collision (UCX)", "MAKO | Sockets"): if bpy.data.collections.get(c): bpy.data.collections[c].hide_render = True # views: (location, look-at, lens) V = { "hero": ((6.4, -7.2, 3.4), (0.0, 0.1, 0.0), 50), "ref": ((-6.0, -7.4, 4.6), (0.0, 0.2, 0.1), 50), "side": ((12.5, 1.4, 0.1), (0.0, 1.4, 0.1), 50), "top": ((0.0, 1.4, 13.0), (0.0, 1.4, 0.0), 45), "front": ((0.9, -10.5, 0.4), (0.0, -1.0, 0.1), 50), "stern": ((-5.0, 9.0, 2.4), (0.0, 2.2, 0.0), 50), "under": ((4.2, -5.6, -5.0), (0.0, 0.4, -0.4), 50), "cockpit": ((1.25, -3.2, 0.85), (0.0, -0.7, -0.15), 30), "clips": ((1.7, -2.1, 2.7), (0.0, -0.09, 1.05), 50), } if "SOCKET_Pilot_Eye" in bpy.data.objects: # what the pilot sees: ahead and a little down e_ = bpy.data.objects["SOCKET_Pilot_Eye"].matrix_world.translation.copy() V["pilot"] = (tuple(e_), tuple(e_ + Vector((0, -1, -0.28))), 14) 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)