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"
/>
+