"""Design preview (headless, nothing saved): the arm's clearance proxies posed by the IK at a few frames, with the ship context, rendered in Workbench. Pictures only for reading the design; they are not the model. Run: blender -b --factory-startup source/ship_context_2026-10-16.blend --python build/design_pose_preview.py -- AFT A previews/design_pose """ import bpy, json, math, sys, os from mathutils import Matrix, Vector HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.dirname(HERE) sys.path.insert(0, HERE) import design_kin as K import design_parts as DP KEY, VER, PREF = sys.argv[sys.argv.index("--") + 1:][:3] FRAMES = [1, 12, 30, 48, 90] D = json.load(open(os.path.join(ROOT, "design.json"))) S = D["scale"]["m_per_ref1_px"] DR = D["doors"][KEY] scn = bpy.context.scene for o in bpy.data.objects: if not o.users_scene: scn.collection.objects.link(o) for n in ("Moonpool | hull lining", "Moonpool | curved dry basin channel"): bpy.data.objects[n].hide_render = True # look into the pocket arm = K.Arm(S) door = bpy.data.objects[DR["door"]] yb, zb = D["carriage"]["centre_y"], D["carriage"]["underside_z"] xt_w, yt_w, zt_w = (Vector(DR["tool_axes_world"][k]) for k in ("x_t", "y_t", "z_t")) tool_c = (K.w2c(tuple(xt_w)), K.w2c(tuple(yt_w)), K.w2c(tuple(zt_w))) o3 = lambda p: Vector((p[0], 0, p[1])) if len(p) == 2 else Vector(p) J2v, J3v, F0v, WCv = o3(K.PX["J2"]) * S, o3(K.PX["J3"]) * S, Vector((-35, 0, 937)) * S, o3(K.PX["WC"]) * S ORIG = {"Turret": (0, 0, 0), "LowerArm": K.PX["J2"], "UpperArm": K.PX["J3"], "Forearm": (-35, 937), "WristHead": K.PX["WC"], "ToolA": K.PX["WC"], "ToolB": K.PX["WC"]} COL = {"Turret": (0.85, 0.85, 0.82, 1), "LowerArm": (0.92, 0.92, 0.9, 1), "UpperArm": (0.88, 0.88, 0.86, 1), "Forearm": (0.9, 0.9, 0.88, 1), "WristHead": (0.2, 0.2, 0.22, 1), "ToolA": (0.25, 0.25, 0.27, 1), "ToolB": (0.25, 0.25, 0.27, 1), "Carriage": (0.15, 0.15, 0.17, 1)} mats = {} for k, c in COL.items(): m = bpy.data.materials.new("prev " + k) m.diffuse_color = c mats[k] = m def prim(kind, name, M, size, mat): if kind == "box": bpy.ops.mesh.primitive_cube_add(size=1.0) o = bpy.context.object o.scale = (2 * size[0], 2 * size[1], 2 * size[2]) else: r, hl, ax = size bpy.ops.mesh.primitive_cylinder_add(vertices=24, radius=r, depth=2 * hl) o = bpy.context.object if ax == "X": o.rotation_euler = (0, math.pi / 2, 0) elif ax == "Y": o.rotation_euler = (math.pi / 2, 0, 0) bpy.context.view_layer.update() o.matrix_world = M @ o.matrix_world o.name = name o.data.materials.append(mat) return o made = [] for f in FRAMES: scn.frame_set(f) dl = door.matrix_world.translation.copy() c = DR["carriage"]["A"] + DR["carriage"]["B"] * dl.x P = dl + Vector(DR["attach_local"]) WC = P - xt_w * D["tools"]["A"]["d_t"] q = K.solve(arm, K.w2c((WC.x - c, WC.y - yb, WC.z - zb)), tool_c, branch5=DR["wrist_branch"]) R0 = Matrix.Translation((0, yb, zb)) @ Matrix.Rotation(math.pi, 4, 'X') M = {"Carriage": R0 @ Matrix.Translation((c, 0, 0))} M["Turret"] = M["Carriage"] @ Matrix.Rotation(q["q1"], 4, 'Z') M["LowerArm"] = M["Turret"] @ Matrix.Translation(J2v) @ Matrix.Rotation(-q["q2"], 4, 'Y') M["UpperArm"] = M["LowerArm"] @ Matrix.Translation(J3v - J2v) @ Matrix.Rotation(-q["q3"], 4, 'Y') M["Forearm"] = M["UpperArm"] @ Matrix.Translation(F0v - J3v) @ Matrix.Rotation(q["q4"], 4, 'X') M["WristHead"] = M["Forearm"] @ Matrix.Translation(WCv - F0v) @ Matrix.Rotation(q["q5"], 4, 'Y') M["ToolA"] = M["ToolB"] = M["WristHead"] @ Matrix.Rotation(q["q6"], 4, 'X') sz = D["carriage"]["size"] made.append((f, prim("box", "prev carriage f%d" % f, M["Carriage"] @ Matrix.Translation((0, 0, -sz["height"] / 2)), (sz["along_rail"] / 2, sz["across"] / 2, sz["height"] / 2), mats["Carriage"]))) for link, lst in DP.PROXIES.items(): if link.startswith("Tool") and link != "Tool" + VER: continue for b in lst: local = Matrix.Translation((Vector(b["c"]) - o3(ORIG[link])) * S) @ Matrix.Rotation(-math.radians(b.get("tilt", 0.0)), 4, 'Y') @ \ Matrix.Rotation(math.radians(b.get("tiltZ", 0.0)), 4, 'Z') if "r" in b: o = prim("cyl", "prev %s %s f%d" % (link, b["n"], f), M[link] @ local, (b["r"] * S, b["hl"] * S, b["ax"]), mats[link]) else: o = prim("box", "prev %s %s f%d" % (link, b["n"], f), M[link] @ local, [v * S for v in b["h"]], mats[link]) made.append((f, o)) # light + cameras, workbench scn.render.engine = 'BLENDER_WORKBENCH' scn.display.shading.light = 'STUDIO' scn.display.shading.color_type = 'MATERIAL' scn.display.shading.show_shadows = True scn.display.shading.show_cavity = True scn.render.resolution_x, scn.render.resolution_y = 1400, 900 side = DR["side"] cx = -5.6 if KEY == "AFT" else 11.7 views = { "inboard": ((cx, 2.5, -5.0), (cx, -6.5, -6.0)), "along_rail": ((cx + side * 6.5, -4.0, -4.6), (cx - side * 1.0, -6.6, -5.8)), "end_on": ((cx - side * 0.2 + side * 9.0 * 0, -1.0, -3.9), (cx, -7.0, -6.2)), } cam_d = bpy.data.cameras.new("prev cam") cam_d.lens = 24 cam = bpy.data.objects.new("prev cam", cam_d) scn.collection.objects.link(cam) scn.camera = cam for f in FRAMES: for ff, o in made: o.hide_render = ff != f scn.frame_set(f) for vn, (eye, look) in views.items(): cam.location = eye d = Vector(look) - Vector(eye) cam.rotation_euler = d.to_track_quat('-Z', 'Y').to_euler() scn.render.filepath = os.path.join(ROOT, "%s_%s_%s_f%03d_%s.png" % (PREF, KEY, VER, f, vn)) bpy.ops.render.render(write_still=True) print("PREVIEW DONE")