70cf45ead4
Deploy / deploy (push) Successful in 7m2s
Blender work is not a shot submission: no Export, no version, no validation, QC or delivery. It is "Ctrl+F12, but on the farm", so it is modelled as what it is — a queue entry and nothing more. That turned out to need very little. Claim, progress, fail and complete-render already tolerated a job with no Export behind them, so a Blender job reuses the whole lifecycle; RenderJob gains projectId, label and submittedBy so a job with no shot code still has something to show. Capability gating is new and was already missing: the claim now filters job types by the machine's reported tools. Without it a workstation without Blender would claim Blender work and fail it repeatedly, burning the attempts while a machine that could have rendered it sat idle. The worker asks for Blender jobs unconditionally and the server refuses them — trusting the worker to self-select would have left the same hole. The runner treats a clean exit with no saved frames as a failure: Blender exits 0 when a frame range is empty or an output path is unwritable, and a job that silently rendered nothing is the worst outcome available. Standalone jobs auto-requeue on a transient failure like any other work; delivery builds are deliberately excluded, since a half-written package wants a human first. Adds the Blender add-on (BlenderPanel/) and its docs. The add-on refuses to submit an unsaved file — the farm opens the .blend from disk, so unsaved changes would render the previous state without warning. Its payload is pinned by a contract test, since add-on and server only ever meet over JSON. Worker 1.5.0. Adds migration 20260815000000_blender_render_jobs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { blenderManifestSchema, createBlenderJobSchema } from "@/lib/render-pipeline/blender";
|
|
|
|
/**
|
|
* The add-on and the server are separate codebases that only meet over JSON,
|
|
* so the exact payload the add-on builds is pinned here. A field renamed on
|
|
* either side otherwise surfaces as a 422 in front of an artist.
|
|
*/
|
|
describe("Blender submission contract", () => {
|
|
// Verbatim shape of scene_summary() in BlenderPanel/vfxreview_submit.py
|
|
const addonManifest = {
|
|
blendFile: "X:/shows/UNG/cg/UNG_110_030_010_smoke.blend",
|
|
scene: "Scene",
|
|
frameStart: 1001,
|
|
frameEnd: 1110,
|
|
outputPath: "X:/shows/UNG/cg/renders/smoke/frame_",
|
|
engine: "CYCLES",
|
|
blenderVersion: "4.2.1",
|
|
};
|
|
|
|
it("accepts what the add-on sends", () => {
|
|
const parsed = blenderManifestSchema.safeParse(addonManifest);
|
|
expect(parsed.success).toBe(true);
|
|
});
|
|
|
|
it("accepts the full submission body", () => {
|
|
const parsed = createBlenderJobSchema.safeParse({
|
|
projectCode: "UNG_S1",
|
|
label: "UNG_110_030_010_smoke",
|
|
manifest: addonManifest,
|
|
submittedByEmail: "chris@twotalesanimation.com",
|
|
urgent: false,
|
|
});
|
|
expect(parsed.success).toBe(true);
|
|
});
|
|
|
|
it("defaults the stage so the add-on need not send it", () => {
|
|
const parsed = blenderManifestSchema.parse(addonManifest);
|
|
expect(parsed.stage).toBe("BLENDER");
|
|
});
|
|
|
|
it("rejects a backwards frame range rather than queueing a no-op render", () => {
|
|
const parsed = blenderManifestSchema.safeParse({ ...addonManifest, frameEnd: 900 });
|
|
expect(parsed.success).toBe(false);
|
|
});
|
|
|
|
it("accepts EEVEE, which is what the add-on reports on a non-Cycles scene", () => {
|
|
expect(blenderManifestSchema.safeParse({ ...addonManifest, engine: "BLENDER_EEVEE_NEXT" }).success).toBe(true);
|
|
});
|
|
});
|