diff --git a/.gitignore b/.gitignore index 1be5757..210fcce 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ wally.lock .DS_Store *.log audio-work/ +tools/shots/ diff --git a/tools/capture_shots.py b/tools/capture_shots.py new file mode 100644 index 0000000..b9c7e81 --- /dev/null +++ b/tools/capture_shots.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +"""Capture a set of Studio viewport angles of the Daikoku PA junction. + +Sets the Edit-mode camera via execute_luau, then pulls a screen_capture for +each shot. Bridge calls are slow (~30-60s each), so this runs as one batch. + +NOTE (corrects an old assumption): screen_capture DOES work on this setup. +It needs capture_id='ScreenCapture_1'. Earlier notes claimed the Studio +viewport could not be screenshotted from wks-victus -- that was wrong. +""" +import shutil +import sys +import time + +sys.path.insert(0, "/tmp") +import rbx # noqa: E402 + +OUT = "/home/jesse/roblox/tokyo-wangan-racing/tools/shots" + +# PA centre measured from the built parts. +CX, CY, CZ = -4116, 51, -7479 + +SHOTS = [ + # name, camera offset from PA centre, look-at offset + ("01_overview_oblique", (520, 340, 520), (0, 0, 0)), + ("02_top_down", (5, 900, 5), (0, 0, 0)), + ("03_underneath_clearance", (330, 20, 300), (0, 30, 0)), + ("04_onramp_low", (-250, 70, 60), (60, 20, -40)), + ("05_offramp_low", (300, 75, -160), (-60, 20, 40)), + ("06_section_side", (900, 120, 40), (0, 25, 0)), +] + +CAM_TPL = """ +local cam = workspace.CurrentCamera +cam.CameraType = Enum.CameraType.Scriptable +cam.FieldOfView = {fov} +cam.CFrame = CFrame.lookAt( + Vector3.new({px}, {py}, {pz}), + Vector3.new({tx}, {ty}, {tz}) +) +return "cam ok" +""" + + +def main(): + made = [] + for name, (ox, oy, oz), (tx, ty, tz) in SHOTS: + fov = 55 if "top_down" not in name else 40 + code = CAM_TPL.format( + fov=fov, + px=CX + ox, + py=CY + oy, + pz=CZ + oz, + tx=CX + tx, + ty=CY + ty, + tz=CZ + tz, + ) + r = rbx.call("execute_luau", {"datamodel_type": "Edit", "code": code}) + if isinstance(r, dict) and r.get("isError"): + print(f"{name}: CAM FAIL {r}") + continue + time.sleep(1.5) + cap = rbx.call( + "screen_capture", {"capture_id": "ScreenCapture_1"}, wait=22, timeout=300 + ) + imgs = cap.get("images") if isinstance(cap, dict) else None + if not imgs: + print(f"{name}: CAPTURE FAIL {str(cap)[:160]}") + continue + dest = f"{OUT}/{name}.jpg" + shutil.copy(imgs[0], dest) + made.append(dest) + print(f"{name}: OK -> {dest}") + print("\nMADE " + str(len(made)) + " shots") + for m in made: + print(m) + + +if __name__ == "__main__": + main()