From 4642ecb11f2c7ef6920b0faebc8b79e5d315434e Mon Sep 17 00:00:00 2001 From: twotalesanimation <80506065+twotalesanimation@users.noreply.github.com> Date: Fri, 14 Aug 2026 11:28:14 +0200 Subject: [PATCH] feat(pipeline): skip the preview build and inherit the previous one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some exports are deliberately framed differently from their review — unscaled or un-punched-in EXRs — and a preview rebuilt from those frames would be the wrong picture. The previous export's MOV is the correct one. skipPreview renders the EXRs and goes straight to READY_FOR_QC. Available at Queue Export (a "No preview" checkbox in the panel, E1 field) and as an option when overriding a validation failure. The inheritance is as important as the skip: deliveryMovPath, the review MP4, the thumbnail and the Version are carried over from the newest earlier export of that shot which has them. Without it the delivery builder — which takes its MOV from deliveryMovPath — would package EXRs with no MOV at all. Where there is nothing to inherit, the export still reaches QC and the timeline says it has no review media. QC is untouched by any of this: a skipped preview still needs a human pass before the export can be delivered. Panel 2.3.0. Adds migration 20260814000000_export_skip_preview. Co-Authored-By: Claude Opus 5 --- RenderPipeline2.md | 8 +++ VFXReviewConnector.jsx | 21 +++++++- VFXReviewConnector.md | 23 ++++++++ .../exports/[exportId]/ExportDetailClient.tsx | 19 ++++++- app/api/ext/exports/route.ts | 4 +- .../[exportId]/override-validation/route.ts | 1 + lib/render-pipeline/exports.ts | 11 +++- lib/render-pipeline/jobs.ts | 11 ++-- lib/render-pipeline/preview.ts | 53 +++++++++++++++++++ lib/render-pipeline/validation.ts | 38 +++++++++---- .../migration.sql | 5 ++ prisma/schema.prisma | 6 +++ 12 files changed, 180 insertions(+), 20 deletions(-) create mode 100644 prisma/migrations/20260814000000_export_skip_preview/migration.sql diff --git a/RenderPipeline2.md b/RenderPipeline2.md index 0ce817d..88a16c4 100644 --- a/RenderPipeline2.md +++ b/RenderPipeline2.md @@ -1131,6 +1131,14 @@ Cost note: the AE preview pass roughly doubles machine time per export. This mat Finalize (E13) then creates the `Version` row server-side (Section 6.4) in pipeline mode — visible internally, never client-shared, no task/shot status changes. +### 9.4 Skipping the preview (`Export.skipPreview`) + +Not every export is framed like its review. A deliverable rendered unscaled or without the punch-in shares a shot and a version history with the review, but a preview rebuilt from *its* frames would show the wrong picture — the earlier export's MOV is the correct one. + +`skipPreview` (set at Queue Export, or per-action when overriding a validation failure) renders the EXRs and goes straight to READY_FOR_QC, **inheriting** `deliveryMovPath`, `previewMovKey`, `thumbnailKey` and `versionId` from the newest earlier export of that shot which has them. Inheritance matters as much as the skip: the delivery builder takes its MOV from `deliveryMovPath`, so an export with none would package EXRs alone. QC is unaffected — the export still needs a human pass before it can be delivered. + +With no earlier export to inherit from, the export still reaches READY_FOR_QC and the timeline records that it has no review media. + --- ## 10. QC Workflow diff --git a/VFXReviewConnector.jsx b/VFXReviewConnector.jsx index 534e4ed..9a7e420 100644 --- a/VFXReviewConnector.jsx +++ b/VFXReviewConnector.jsx @@ -32,7 +32,7 @@ var SETTINGS_KEY = "cfg"; // Bump on every change deployed to a workstation. The panel reports this to // the server on load, so Pipeline → Machines shows which version each machine // is actually running and flags the ones left behind. -var PANEL_VERSION = "2.2.1"; +var PANEL_VERSION = "2.3.0"; (function VFXReviewConnector(thisObj) { var uiState = { @@ -44,6 +44,7 @@ var PANEL_VERSION = "2.2.1"; lastActionText: null, pipelineStatusText: null, urgentCheckbox: null, + skipPreviewCheckbox: null, submissionNoteField: null, qcList: null, qcNotesField: null, @@ -3851,6 +3852,13 @@ var PANEL_VERSION = "2.2.1"; force: force ? true : false }; + // Renders the EXRs and inherits the previous export's review media + // rather than rebuilding a preview from frames that are not the review + // framing (unscaled / un-punched-in deliverables). + if (uiState.skipPreviewCheckbox && uiState.skipPreviewCheckbox.value) { + body.skipPreview = true; + } + if (CONFIG.artistEmail) { body.submittedByEmail = CONFIG.artistEmail; } @@ -3942,7 +3950,9 @@ var PANEL_VERSION = "2.2.1"; if (lastQueued) { updateStatus(comps.length, "Queued " + lastQueued.versionString + - (urgent ? " (urgent)" : "") + " on render farm. " + + (urgent ? " (urgent)" : "") + + ((uiState.skipPreviewCheckbox && uiState.skipPreviewCheckbox.value) ? " (no preview)" : "") + + " on render farm. " + result.queued + " queued, " + result.skipped + " skipped."); setPipelineStatus(formatExportStatus(lastShotCode, lastQueued)); startExportPolling(lastShotCode); @@ -4410,6 +4420,13 @@ var PANEL_VERSION = "2.2.1"; pipelineRow1.spacing = 6; queueExportButton = pipelineRow1.add("button", undefined, "Queue Export"); uiState.urgentCheckbox = pipelineRow1.add("checkbox", undefined, "Urgent"); + uiState.skipPreviewCheckbox = pipelineRow1.add("checkbox", undefined, "No preview"); + uiState.skipPreviewCheckbox.helpTip = + "Render the EXRs but build no preview, reusing the previous export's\n" + + "MOV and review media.\n\n" + + "For deliverables framed differently from the review — unscaled or\n" + + "un-punched-in EXRs — where a preview rebuilt from these frames\n" + + "would be the wrong picture."; var slateNoteGroup = pipelinePanel.add("group"); slateNoteGroup.orientation = "row"; diff --git a/VFXReviewConnector.md b/VFXReviewConnector.md index 80d732b..93ab933 100644 --- a/VFXReviewConnector.md +++ b/VFXReviewConnector.md @@ -198,6 +198,29 @@ see [Diagnostics and logging](#diagnostics-and-logging). --- +## Render Pipeline + +### Queue Export + +Saves the project, builds the render manifest from the selected comp and the +shot record, and submits it to the farm. + +| Option | Effect | +|---|---| +| **Urgent** | Priority 20 — claimable outside the render window, ahead of normal jobs | +| **No preview** | Renders the EXRs but builds no preview, inheriting the previous export's MOV, review MP4 and thumbnail | + +**No preview** exists for deliverables framed differently from the review — +unscaled or un-punched-in EXRs. A preview rebuilt from those frames would be +the wrong picture, while the earlier export's MOV is already correct, so it +carries over instead. The export still goes through QC, and the delivery +package still gets a MOV. + +Leave it unticked for an ordinary submission: the preview is what QC and the +client review see. + +--- + ## QC Technical QC on the **rendered EXRs**, not the H.264 preview — the point is diff --git a/app/(dashboard)/pipeline/exports/[exportId]/ExportDetailClient.tsx b/app/(dashboard)/pipeline/exports/[exportId]/ExportDetailClient.tsx index 23cccb7..ba54937 100644 --- a/app/(dashboard)/pipeline/exports/[exportId]/ExportDetailClient.tsx +++ b/app/(dashboard)/pipeline/exports/[exportId]/ExportDetailClient.tsx @@ -108,6 +108,7 @@ export function ExportDetailClient({ exportId }: { exportId: string }) { const queryClient = useQueryClient(); const [overrideOpen, setOverrideOpen] = useState(false); const [overrideReason, setOverrideReason] = useState(""); + const [overrideSkipPreview, setOverrideSkipPreview] = useState(false); const [overriding, setOverriding] = useState(false); const { data, isLoading, error } = useQuery({ @@ -135,7 +136,7 @@ export function ExportDetailClient({ exportId }: { exportId: string }) { const res = await fetch(`/api/render/exports/${exportId}/override-validation`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ reason: overrideReason }), + body: JSON.stringify({ reason: overrideReason, skipPreview: overrideSkipPreview }), }); const body = await res.json().catch(() => ({})); if (!res.ok) { @@ -240,6 +241,21 @@ export function ExportDetailClient({ exportId }: { exportId: string }) { placeholder="e.g. plate unreachable from the render node; timecode checked by hand against the EDL" className="w-full rounded-md border border-zinc-800 bg-zinc-950 px-3 py-2 text-sm text-zinc-200 placeholder:text-zinc-600" /> +