"""Headless runner for dra_ship_install on a ship file (a work copy beside the real one, so relative paths hold). blender -b --factory-startup -t 8 --python run_ship_install.py -- --stage install --report install: apply(), check(), save the file in place (only when the check has no clash) blender ... -- --stage render --out Workbench pictures of both arms over the timeline (file not saved) blender ... -- --stage check --report check() only (file not saved) """ import json import math import os import sys import time import bpy from mathutils import Vector HERE = os.path.dirname(os.path.abspath(__file__)) if HERE not in sys.path: sys.path.insert(0, HERE) import dra_ship_install as inst argv = sys.argv[sys.argv.index("--") + 1:] def arg(name, default=None): return argv[argv.index(name) + 1] if name in argv else default stage = arg("--stage") t0 = time.time() if stage in ("install", "check"): out = {"file": bpy.data.filepath, "stage": stage} if stage == "install": out["apply"] = inst.apply() out["check"] = inst.check(step=int(arg("--step", "1"))) out["seconds"] = round(time.time() - t0, 1) ok = not out["check"]["clashes"] and all( p["plate_to_pad_mm"] < 0.5 and p["plate_to_pad_deg"] < 0.05 and p["pad_to_door_mm"] < 0.5 for p in out["check"]["pose"].values()) if stage == "install": ok = ok and not out["apply"]["drivers_bad"] out["ok"] = ok if stage == "install" and ok: scn = bpy.data.scenes[inst.SCENE] scn.frame_set(1) bpy.ops.wm.save_mainfile() out["saved"] = bpy.data.filepath json.dump(out, open(arg("--report"), "w"), indent=1) print("RESULT ok=%s clashes=%d seconds=%.0f" % (ok, len(out["check"]["clashes"]), out["seconds"])) elif stage == "render": folder = arg("--out") os.makedirs(folder, exist_ok=True) scn = bpy.data.scenes[inst.SCENE] r = scn.render r.engine = 'BLENDER_WORKBENCH' r.resolution_x, r.resolution_y, r.resolution_percentage = 1280, 720, 100 r.film_transparent = False sh = scn.display.shading sh.light = 'STUDIO' sh.color_type = 'MATERIAL' sh.show_cavity = True sh.cavity_type = 'BOTH' sh.show_object_outline = True sh.show_shadows = True scn.display.shadow_focus = 0.5 cam_data = bpy.data.cameras.new("tmp cam") cam_data.lens = 20 cam_data.clip_start = 0.05 cam = bpy.data.objects.new("tmp cam", cam_data) scn.collection.objects.link(cam) scn.camera = cam def look(eye, target): cam.location = Vector(eye) d = Vector(target) - Vector(eye) cam.rotation_euler = d.to_track_quat('-Z', 'Y').to_euler() # the carriage's x at a frame, from the arm itself (follows the door) def carriage_x(d): dg = scn.view_layers[0].depsgraph dg.update() return bpy.data.objects["DoorArm_%s_B | carriage" % d].evaluated_get(dg).matrix_world.translation.x shots = [] for d, frames in (("AFT", (1, 12, 30, 48, 90)), ("FORE", (1, 48))): s = 1.0 if d == "AFT" else -1.0 # AFT pocket toward -x for f in frames: shots.append((d, f, "inboard")) shots.append((d, f, "along")) done = [] for d, f, view in shots: scn.frame_set(f) x = carriage_x(d) s = 1.0 if d == "AFT" else -1.0 if view == "inboard": # from the basin side under the deck, looking outboard at the arm and its door look((x + s * 1.6, -1.2, -5.0), (x + s * 0.6, -6.9, -5.6)) else: # from the moon pool end of the pocket, along the rail look((x + s * 4.2, -3.6, -4.6), (x - s * 0.2, -6.4, -5.3)) bpy.context.view_layer.update() fn = os.path.join(folder, "ship_%s_B_f%03d_%s.png" % (d, f, view)) r.filepath = fn bpy.ops.render.render(write_still=True, scene=scn.name) done.append(os.path.basename(fn)) print("RENDERED", len(done), round(time.time() - t0, 1))