"""Renders the rigged carousel posed through its handles, and a second pass with the joints drawn as markers (bones do not render, so sticks and balls stand in for them). compose_rig_preview.py lays the two over each other. blender.exe -b --python-exit-code 1 --python render_rig_preview.py """ import bpy, bmesh, math, os from mathutils import Vector, Matrix HERE = os.path.dirname(os.path.abspath(__file__)) OUT = os.path.dirname(HERE) PREV = os.path.join(OUT, "previews") bpy.ops.wm.open_mainfile(filepath=os.path.join(OUT, "DockArm_Carousel_Rigged.blend")) sc = bpy.context.scene vl = bpy.context.view_layer rig = bpy.data.objects["DockArm_Rig"] P = rig.pose.bones P["Turret"].rotation_euler.y = math.radians(-12) P["CTRL_Arm1_Hoist"]["reach_in"] = 2.34 P["CTRL_Arm1_Hoist"].location.y = 8.0 # arm 1 on its way into the pool: the boom joint at work P["CTRL_Arm3_Hoist"].location.y = 5.3 P["CTRL_Arm3_Claws"].rotation_euler.x = math.radians(25) rig.update_tag(); vl.update() E = rig.evaluated_get(bpy.context.evaluated_depsgraph_get()).pose.bones def emit(name, col): m = bpy.data.materials.new(name) m.use_nodes = True b = m.node_tree.nodes["Principled BSDF"] b.inputs["Base Color"].default_value = (0, 0, 0, 1) b.inputs["Emission Color"].default_value = col + (1,) b.inputs["Emission Strength"].default_value = 3.0 return m M_JOINT, M_BONE, M_CTRL = emit("_joint", (1.0, 0.42, 0.03)), emit("_bone", (1.0, 0.85, 0.2)), emit("_ctrl", (0.15, 1.0, 0.35)) bm = bmesh.new() def stick(a, b, r, mi, seg=8): d = b - a if d.length < 1e-6: return g = bmesh.ops.create_cone(bm, cap_ends=True, segments=seg, radius1=r, radius2=r, depth=d.length, matrix=Matrix.Translation((a + b) / 2) @ d.to_track_quat('Z', 'Y').to_matrix().to_4x4()) for f in {f for v in g["verts"] for f in v.link_faces}: f.material_index = mi def ball(p, r, mi): g = bmesh.ops.create_uvsphere(bm, u_segments=14, v_segments=8, radius=r, matrix=Matrix.Translation(p)) for f in {f for v in g["verts"] for f in v.link_faces}: f.material_index = mi for pb in E: b = rig.data.bones[pb.name] if b.use_deform and "_Hose" in pb.name: continue # the hose fan bones sit on the joints they shadow if b.use_deform: big = pb.name.endswith(("_Boom", "_Upper", "_Lower", "_Head")) or pb.name == "Turret" stick(pb.head.copy(), pb.tail.copy(), 0.03 if big else 0.015, 1) ball(pb.head.copy(), 0.11 if big else 0.05, 0) elif pb.name.startswith("CTRL_"): M = pb.matrix if pb.name.endswith("_Hoist"): c = [M @ Vector((x, 0, 1.30 + z)) for x, z in ((-0.22, -0.22), (0.22, -0.22), (0.22, 0.22), (-0.22, 0.22))] else: c = [M @ Vector((0.15 * math.cos(2 * math.pi * i / 16), 0, 0.15 * math.sin(2 * math.pi * i / 16))) for i in range(16)] for i in range(len(c)): stick(c[i], c[(i + 1) % len(c)], 0.018, 2, 6) me = bpy.data.meshes.new("_markers") bm.to_mesh(me); bm.free() for m in (M_JOINT, M_BONE, M_CTRL): me.materials.append(m) markers = bpy.data.objects.new("_markers", me) sc.collection.objects.link(markers) cam = sc.camera def look(loc, tgt, lens): cam.location = loc cam.rotation_euler = (Vector(tgt) - Vector(loc)).to_track_quat('-Z', 'Y').to_euler() cam.data.lens = lens for _eng in ('BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'): # joint diagrams: EEVEE is enough (the model file itself is set to Cycles since 2026-10-13) try: sc.render.engine = _eng break except Exception: continue try: sc.eevee.taa_render_samples = 48 except Exception: pass models = [o for o in bpy.data.objects if o.type == 'MESH' and o.name.startswith("SK_")] for tag, loc, tgt, lens in (("above", (9.5, -10.5, 6.5), (0.2, -0.6, -1.6), 30), ("below", (10.5, -14.0, -6.2), (0.9, -1.6, -2.0), 28)): look(loc, tgt, lens) markers.hide_render = True for o in models: o.hide_render = False sc.render.film_transparent = False sc.render.image_settings.color_mode = 'RGB' sc.render.filepath = os.path.join(PREV, "_rig_%s_model.png" % tag) bpy.ops.render.render(write_still=True) markers.hide_render = False for o in models: o.hide_render = True sc.render.film_transparent = True sc.render.image_settings.color_mode = 'RGBA' sc.render.filepath = os.path.join(PREV, "_rig_%s_markers.png" % tag) bpy.ops.render.render(write_still=True) print("RIG PREVIEW PASSES DONE")