updated export paths
Deploy / deploy (push) Successful in 6m32s

This commit is contained in:
twotalesanimation
2026-08-07 12:07:16 +02:00
parent 7caf41e17c
commit 7dcb9303e5
7 changed files with 452 additions and 90 deletions
+338 -85
View File
@@ -3,22 +3,27 @@
Adobe After Effects 2024+ dockable ScriptUI panel
*/
var TOKEN = "am3O0PWUtqMJkAqsZ+bO7lho4cxQItxgukF6FHteAx4=";
var BASE_URL = "http://localhost:3000";
var PROJECT_CODE = "UNG_S1";
var EXPORT_ROOT = "V:/_EXPORTS/UNG";
var FOOTAGE_ROOT = "V:/_FOOTAGE/UNG";
var BANNER_IMAGE_PATH = "V:/VFXReviewConnector/banner.png";
// Optional: artist email for pipeline export attribution (Queue Export)
var ARTIST_EMAIL = "chris@twotalesanimation.com";
// Handle frames included in plates before the shot's source TC in-point.
// Used to derive the plate's own start timecode from the API's timecodeStart.
var HANDLE_FRAMES = 8;
// Default config — overridden by stored settings at runtime
var CONFIG = {
baseUrl: "http://localhost:3000",
token: "",
exportRoot: "",
footageRoot: "",
bannerImagePath:"",
artistEmail: "",
handleFrames: 8,
lastProjectCode:""
};
var SETTINGS_GROUP = "VFXReviewConnector";
var SETTINGS_KEY = "cfg";
(function VFXReviewConnector(thisObj) {
var uiState = {
projectDropdown: null,
episodeDropdown: null,
shotDropdown: null,
episodeLabel: null,
selectionText: null,
lastActionText: null,
pipelineStatusText: null,
@@ -26,6 +31,8 @@ var HANDLE_FRAMES = 8;
submissionNoteField: null
};
var CURRENT_PROJECT = null; // { code, name, showId, projectType }
var pipelineState = {
pollShotCode: null,
pollTaskId: 0,
@@ -57,6 +64,14 @@ var HANDLE_FRAMES = 8;
return null;
}
// ExtendScript is ES3 — String.prototype.trim does not exist.
function trimText(value) {
if (value === null || value === undefined) {
return "";
}
return String(value).replace(/^\s+/, "").replace(/\s+$/, "");
}
function getSelectedComps() {
var comps = [];
var selection;
@@ -83,12 +98,12 @@ var HANDLE_FRAMES = 8;
var parsed;
try {
url = BASE_URL + "/api/ext/shots/lookup" +
url = CONFIG.baseUrl + "/api/ext/shots/lookup" +
"?shotCode=" + encodeURIComponent(shotCode) +
"&projectCode=" + PROJECT_CODE;
"&projectCode=" + (CURRENT_PROJECT ? CURRENT_PROJECT.code : "");
command = "curl -s " +
"-H \"Authorization: Bearer " + TOKEN + "\" " +
"-H \"Authorization: Bearer " + CONFIG.token + "\" " +
"-H \"Accept: application/json\" " +
"\"" + url + "\"";
@@ -112,7 +127,7 @@ var HANDLE_FRAMES = 8;
try {
command = "curl -s " +
"-H \"Authorization: Bearer " + TOKEN + "\" " +
"-H \"Authorization: Bearer " + CONFIG.token + "\" " +
"-H \"Accept: application/json\" " +
"\"" + url + "\"";
@@ -229,19 +244,39 @@ var HANDLE_FRAMES = 8;
}
function getEpisodesFromAPI() {
var url = BASE_URL + "/api/ext/projects/" + PROJECT_CODE + "/episodes";
var url = CONFIG.baseUrl + "/api/ext/projects/" + (CURRENT_PROJECT ? CURRENT_PROJECT.code : "") + "/episodes";
var data = getAPIData(url);
return getListFromResponse(data, "episodes");
}
function getShotsFromAPI(episodeValue) {
var url = BASE_URL + "/api/ext/projects/" + PROJECT_CODE + "/shots?episode=" + encodeURIComponent(episodeValue);
function getShotsFromAPI(value) {
var isEpisodic = CURRENT_PROJECT && CURRENT_PROJECT.projectType === "EPISODIC";
var param = isEpisodic ? "episode" : "scene";
var url = CONFIG.baseUrl + "/api/ext/projects/" + (CURRENT_PROJECT ? CURRENT_PROJECT.code : "") + "/shots?" + param + "=" + encodeURIComponent(value);
var data = getAPIData(url);
return getListFromResponse(data, "shots");
}
function getScenesFromAPI() {
var url = CONFIG.baseUrl + "/api/ext/projects/" + (CURRENT_PROJECT ? CURRENT_PROJECT.code : "") + "/shots?limit=500";
var data = getAPIData(url);
var shots = getListFromResponse(data, "shots");
var seen = {};
var scenes = [];
for (var i = 0; i < shots.length; i += 1) {
var sc = shots[i].scene || "";
if (sc && !seen[sc]) { seen[sc] = true; scenes.push(sc); }
}
scenes.sort();
return scenes;
}
function getProjectsFromAPI() {
var url = CONFIG.baseUrl + "/api/ext/projects";
var data = getAPIData(url);
return getListFromResponse(data, "projects");
}
function normalizeShotData(data) {
if (!data) {
return null;
@@ -270,6 +305,16 @@ var HANDLE_FRAMES = 8;
return null;
}
function getShowExportRoot() {
var showId = CURRENT_PROJECT ? (CURRENT_PROJECT.showId || CURRENT_PROJECT.code) : "";
return CONFIG.exportRoot + (showId ? "/" + showId : "");
}
function getShowFootageRoot() {
var showId = CURRENT_PROJECT ? (CURRENT_PROJECT.showId || CURRENT_PROJECT.code) : "";
return CONFIG.footageRoot + (showId ? "/" + showId : "");
}
function ensureFolder(path) {
var folder = new Folder(path);
var parent;
@@ -397,9 +442,9 @@ var HANDLE_FRAMES = 8;
}
if (apiShot && apiShot.timecodeStart) {
frames = parseTimecodeToFrames(apiShot.timecodeStart, fps) - HANDLE_FRAMES;
frames = parseTimecodeToFrames(apiShot.timecodeStart, fps) - CONFIG.handleFrames;
if (frames > 0) {
return { frames: frames, source: "API timecode " + apiShot.timecodeStart + " -" + HANDLE_FRAMES + "f" };
return { frames: frames, source: "API timecode " + apiShot.timecodeStart + " -" + CONFIG.handleFrames + "f" };
}
}
@@ -462,11 +507,11 @@ var HANDLE_FRAMES = 8;
}
function getEpisodeRootFolder(episodeCode) {
return new Folder(FOOTAGE_ROOT + "/" + episodeCode);
return new Folder(getShowFootageRoot() + "/" + episodeCode);
}
function getEpisodeCodesFromDisk() {
var root = new Folder(FOOTAGE_ROOT);
var root = new Folder(getShowFootageRoot());
var folders;
var codes = [];
var i;
@@ -867,7 +912,7 @@ var HANDLE_FRAMES = 8;
disableLayerByName(comp, "UNG_VFX_OVERLAY");
disableLayerByName(comp, "_SHOW LUT");
folderPath = EXPORT_ROOT + "/" + shotCode;
folderPath = getShowExportRoot() + "/" + shotCode;
if (!ensureFolder(folderPath)) {
addFailure(result, comp.name, "Could not create output folder");
continue;
@@ -1108,13 +1153,19 @@ var HANDLE_FRAMES = 8;
var episodeCode;
var shotCode;
var shotComp;
var isEpisodic = CURRENT_PROJECT && CURRENT_PROJECT.projectType === "EPISODIC";
if (!uiState.episodeDropdown || !uiState.episodeDropdown.selection || !uiState.shotDropdown || !uiState.shotDropdown.selection) {
updateStatus(getSelectedComps().length, "Choose an episode and shot first.");
updateStatus(getSelectedComps().length, "Choose a " + (isEpisodic ? "episode" : "scene") + " and shot first.");
return;
}
episodeCode = uiState.episodeDropdown.selection.episodeCode || deriveEpisodeCode(uiState.episodeDropdown.selection.text);
// For episodic, derive full episode code; for standard pass the selected scene value
if (isEpisodic) {
episodeCode = uiState.episodeDropdown.selection.episodeCode || deriveEpisodeCode(uiState.episodeDropdown.selection.text);
} else {
episodeCode = uiState.episodeDropdown.selection.filterValue || uiState.episodeDropdown.selection.text;
}
shotCode = uiState.shotDropdown.selection.shotCode || uiState.shotDropdown.selection.text;
app.beginUndoGroup("VFXReview Connector - Build Shot");
@@ -1193,7 +1244,7 @@ var HANDLE_FRAMES = 8;
var outputPath;
app.beginUndoGroup("VFXReview Connector - Queue " + label);
if (!ensureFolder(EXPORT_ROOT)) {
if (!ensureFolder(getShowExportRoot())) {
app.endUndoGroup();
updateStatus(comps.length, "Could not create export folder.");
return;
@@ -1231,7 +1282,7 @@ var HANDLE_FRAMES = 8;
}
updateStatus(comps.length, "Queueing " + label + " for " + shotCode + " (" + (i + 1) + " of " + comps.length + ")");
outputPath = EXPORT_ROOT + "/" + shotCode + "_cmp_TT_" + (apiData.shot.shotVersion || "v001") + "." + extension;
outputPath = getShowExportRoot() + "/" + shotCode + "_cmp_TT_" + (apiData.shot.shotVersion || "v001") + "." + extension;
if (queueCompWithOutput(previewComp, templateName, outputPath)) {
queued += 1;
result.queued += 1;
@@ -1272,7 +1323,7 @@ var HANDLE_FRAMES = 8;
disableLayerByName(comp, "UNG_VFX_OVERLAY");
disableLayerByName(comp, "_SHOW LUT");
folderPath = EXPORT_ROOT + "/" + shotCode;
folderPath = getShowExportRoot() + "/" + shotCode;
if (!ensureFolder(folderPath)) {
addFailure(result, comp.name, "Could not create output folder");
continue;
@@ -1333,7 +1384,7 @@ var HANDLE_FRAMES = 8;
continue;
}
exportFolder = new Folder(EXPORT_ROOT + "/" + shot.shotCode);
exportFolder = new Folder(getShowExportRoot() + "/" + shot.shotCode);
if (!exportFolder.exists) {
updateStatus(comps.length, "Export folder not found: " + exportFolder.fsName);
skipped += 1;
@@ -1545,7 +1596,7 @@ var HANDLE_FRAMES = 8;
expectedBaseName = shotCode + "_cmp_TT_" + (shot.shotVersion || "v001");
exrOutputBase = shot.exrOutput || "";
sourceFolder = new Folder(EXPORT_ROOT + "/" + shotCode);
sourceFolder = new Folder(getShowExportRoot() + "/" + shotCode);
if (!sourceFolder.exists) {
addFailure(result, comp.name, "Source export folder not found: " + sourceFolder.fsName);
continue;
@@ -1586,7 +1637,7 @@ var HANDLE_FRAMES = 8;
var episodeCode = shotCode.substring(0, 7);
var deliveryDate = getDateString().replace(/\//g, "");
deliveryFolderPath = EXPORT_ROOT + "/DELIVERY/" + episodeCode + "/" + deliveryDate + "_Delivery/" + shotCode;
deliveryFolderPath = getShowExportRoot() + "/DELIVERY/" + episodeCode + "/" + deliveryDate + "_Delivery/" + shotCode;
if (!ensureFolder(deliveryFolderPath)) {
addFailure(result, comp.name, "Could not create delivery folder");
continue;
@@ -1731,7 +1782,7 @@ var HANDLE_FRAMES = 8;
// Not in project — import from disk
if (!piclockFootage) {
piclocksDir = new Folder(FOOTAGE_ROOT + "/_PICLOCKS");
piclocksDir = new Folder(getShowFootageRoot() + "/_PICLOCKS");
if (!piclocksDir.exists) {
updateStatus(getSelectedComps().length, "PICLOCKS folder not found: " + piclocksDir.fsName);
return;
@@ -1978,7 +2029,7 @@ var HANDLE_FRAMES = 8;
}
function populateShotDropdown() {
var episodeValue;
var filterValue;
var shots;
var i;
var shotCode;
@@ -1988,8 +2039,8 @@ var HANDLE_FRAMES = 8;
return;
}
episodeValue = uiState.episodeDropdown.selection.episodeValue || uiState.episodeDropdown.selection.text;
shots = getShotsFromAPI(episodeValue);
filterValue = uiState.episodeDropdown.selection.filterValue || uiState.episodeDropdown.selection.text;
shots = getShotsFromAPI(filterValue);
clearDropdown(uiState.shotDropdown);
for (i = 0; i < shots.length; i += 1) {
@@ -2008,38 +2059,132 @@ var HANDLE_FRAMES = 8;
}
}
function populateEpisodeDropdown() {
var episodes = getEpisodesFromAPI();
function populateEpisodeOrSceneDropdown() {
var isEpisodic = CURRENT_PROJECT && CURRENT_PROJECT.projectType === "EPISODIC";
var i;
var label;
var episodeValue;
var item;
if (!uiState.episodeDropdown || !uiState.shotDropdown) {
return;
}
if (!uiState.episodeDropdown || !uiState.shotDropdown) { return; }
clearDropdown(uiState.episodeDropdown);
clearDropdown(uiState.shotDropdown);
for (i = 0; i < episodes.length; i += 1) {
label = getEpisodeLabel(episodes[i]);
episodeValue = getEpisodeValue(episodes[i]);
if (label && episodeValue) {
item = uiState.episodeDropdown.add("item", label);
item.episodeValue = episodeValue;
item.episodeCode = deriveEpisodeCode(label);
if (!/^[A-Z]{3}_\d{3}$/.test(item.episodeCode)) {
item.episodeCode = deriveEpisodeCode(episodeValue);
if (uiState.episodeLabel) {
uiState.episodeLabel.text = isEpisodic ? "Ep:" : "Scene:";
}
if (isEpisodic) {
var episodes = getEpisodesFromAPI();
for (i = 0; i < episodes.length; i += 1) {
var label = getEpisodeLabel(episodes[i]);
var episodeValue = getEpisodeValue(episodes[i]);
if (label && episodeValue) {
item = uiState.episodeDropdown.add("item", label);
item.filterValue = episodeValue;
item.episodeCode = deriveEpisodeCode(label);
if (!/^[A-Z]{3}_\d{3}$/.test(item.episodeCode)) {
item.episodeCode = deriveEpisodeCode(episodeValue);
}
}
}
} else {
var scenes = getScenesFromAPI();
for (i = 0; i < scenes.length; i += 1) {
item = uiState.episodeDropdown.add("item", scenes[i]);
item.filterValue = scenes[i];
item.episodeCode = null;
}
}
if (uiState.episodeDropdown.items.length > 0) {
uiState.episodeDropdown.selection = 0;
populateShotDropdown();
} else {
updateStatus(getSelectedComps().length, "No episodes found from API.");
updateStatus(getSelectedComps().length, isEpisodic ? "No episodes found." : "No scenes found.");
}
}
function populateEpisodeDropdown() {
populateEpisodeOrSceneDropdown();
}
function populateProjectDropdown() {
var projects;
var i;
var p;
var item;
var selectIdx = 0;
if (!uiState.projectDropdown) { return; }
projects = getProjectsFromAPI();
clearDropdown(uiState.projectDropdown);
clearDropdown(uiState.episodeDropdown);
clearDropdown(uiState.shotDropdown);
for (i = 0; i < projects.length; i += 1) {
p = projects[i];
item = uiState.projectDropdown.add("item", p.code + " — " + p.name);
item.projectData = p;
if (p.code === CONFIG.lastProjectCode) { selectIdx = i; }
}
if (uiState.projectDropdown.items.length > 0) {
uiState.projectDropdown.selection = selectIdx;
CURRENT_PROJECT = uiState.projectDropdown.items[selectIdx].projectData;
populateEpisodeOrSceneDropdown();
} else {
updateStatus(0, "No projects found — check server URL and token in Settings.");
}
}
function openSettingsDialog() {
var dlg = new Window("dialog", "VFXReview Connector — Settings", undefined);
dlg.orientation = "column";
dlg.alignChildren = ["fill", "top"];
dlg.spacing = 6;
dlg.margins = 16;
function addField(parent, lbl, val) {
var grp = parent.add("group");
grp.orientation = "row";
grp.alignChildren = ["fill", "center"];
var label = grp.add("statictext", undefined, lbl);
label.preferredSize = [110, 20];
var field = grp.add("edittext", undefined, val || "");
field.preferredSize = [280, 22];
return field;
}
var fUrl = addField(dlg, "Server URL:", CONFIG.baseUrl);
var fToken = addField(dlg, "API Token:", CONFIG.token);
var fExport = addField(dlg, "Export Root:", CONFIG.exportRoot);
var fFootage= addField(dlg, "Footage Root:", CONFIG.footageRoot);
var fBanner = addField(dlg, "Banner Path:", CONFIG.bannerImagePath);
var fEmail = addField(dlg, "Artist Email:", CONFIG.artistEmail);
var fHandles= addField(dlg, "Handle Frames:", String(CONFIG.handleFrames));
var btnRow = dlg.add("group");
btnRow.orientation = "row";
btnRow.alignChildren = ["fill", "center"];
btnRow.spacing = 8;
// Using named "ok"/"cancel" — the only reliable way to capture dialog result in ScriptUI
btnRow.add("button", undefined, "Save", { name: "ok" });
btnRow.add("button", undefined, "Cancel", { name: "cancel" });
dlg.layout.layout(true);
dlg.center();
if (dlg.show() === 1) {
CONFIG.baseUrl = trimText(fUrl.text) || CONFIG.baseUrl;
CONFIG.token = trimText(fToken.text);
CONFIG.exportRoot = trimText(fExport.text);
CONFIG.footageRoot = trimText(fFootage.text);
CONFIG.bannerImagePath = trimText(fBanner.text);
CONFIG.artistEmail = trimText(fEmail.text);
CONFIG.handleFrames = parseInt(fHandles.text, 10) || 8;
saveConfig();
populateProjectDropdown();
}
}
@@ -2063,7 +2208,7 @@ var HANDLE_FRAMES = 8;
}
function patchShotVersion(shotId, newVersion) {
var url = BASE_URL + "/api/ext/shots/" + shotId;
var url = CONFIG.baseUrl + "/api/ext/shots/" + shotId;
// Inner double-quotes escaped for Windows cmd.exe
var body = "{\\\"shotVersion\\\":\\\"" + newVersion + "\\\"}";
var command;
@@ -2071,7 +2216,7 @@ var HANDLE_FRAMES = 8;
try {
command = "curl -s -X PATCH " +
"-H \"Authorization: Bearer " + TOKEN + "\" " +
"-H \"Authorization: Bearer " + CONFIG.token + "\" " +
"-H \"Content-Type: application/json\" " +
"-H \"Accept: application/json\" " +
"-d \"" + body + "\" " +
@@ -2188,7 +2333,7 @@ var HANDLE_FRAMES = 8;
shot = data.shot;
fps = shot.fps || comps[i].frameRate || 24;
frameNumber = parseTimecodeToFrames(shot.timecodeStart, fps) - HANDLE_FRAMES;
frameNumber = parseTimecodeToFrames(shot.timecodeStart, fps) - CONFIG.handleFrames;
setCompStartFrame(comps[i], frameNumber);
updated += 1;
}
@@ -2263,6 +2408,40 @@ var HANDLE_FRAMES = 8;
return "{" + parts.join(",") + "}";
}
function getConfigPath() {
return Folder.userData.fsName + "/VFXReviewConnector/config.json";
}
function loadConfig() {
try {
var f = new File(getConfigPath());
if (f.exists) {
f.encoding = "UTF-8";
f.open("r");
var raw = f.read();
f.close();
var parsed = parseJSON(raw);
if (parsed) {
for (var k in parsed) {
if (parsed.hasOwnProperty(k)) CONFIG[k] = parsed[k];
}
}
}
} catch (e) {}
}
function saveConfig() {
try {
var dir = new Folder(Folder.userData.fsName + "/VFXReviewConnector");
if (!dir.exists) { dir.create(); }
var f = new File(getConfigPath());
f.encoding = "UTF-8";
f.open("w");
f.write(jsonStringify(CONFIG));
f.close();
} catch (e) {}
}
// POST JSON via a temp file (-d @file) — avoids cmd.exe quote-escaping
// problems for large bodies. Returns { status: httpCode, data: parsed }.
function postJSON(url, bodyObject) {
@@ -2283,7 +2462,7 @@ var HANDLE_FRAMES = 8;
bodyFile.close();
command = "curl -s -X POST " +
"-H \"Authorization: Bearer " + TOKEN + "\" " +
"-H \"Authorization: Bearer " + CONFIG.token + "\" " +
"-H \"Content-Type: application/json\" " +
"-H \"Accept: application/json\" " +
"-d \"@" + bodyFile.fsName + "\" " +
@@ -2315,9 +2494,9 @@ var HANDLE_FRAMES = 8;
}
function getLatestExportData(shotCode) {
var url = BASE_URL + "/api/ext/exports/latest" +
var url = CONFIG.baseUrl + "/api/ext/exports/latest" +
"?shotCode=" + encodeURIComponent(shotCode) +
"&projectCode=" + PROJECT_CODE;
"&projectCode=" + (CURRENT_PROJECT ? CURRENT_PROJECT.code : "");
return getAPIData(url);
}
@@ -2501,7 +2680,7 @@ var HANDLE_FRAMES = 8;
return {
manifestVersion: 1,
projectCode: PROJECT_CODE,
projectCode: CURRENT_PROJECT ? CURRENT_PROJECT.code : "",
shotCode: shot.shotCode,
shotId: shot.id,
aepPath: aepPath,
@@ -2524,20 +2703,20 @@ var HANDLE_FRAMES = 8;
force: force ? true : false
};
if (ARTIST_EMAIL) {
body.submittedByEmail = ARTIST_EMAIL;
if (CONFIG.artistEmail) {
body.submittedByEmail = CONFIG.artistEmail;
}
if (urgent) {
body.priority = 20;
}
// submissionNote is a per-export override; if blank the server falls back to the shot's slate default.
if (uiState.submissionNoteField) {
var note = uiState.submissionNoteField.text;
if (note && note.trim()) {
var note = trimText(uiState.submissionNoteField.text);
if (note) {
body.submissionNote = note;
}
}
return postJSON(BASE_URL + "/api/ext/exports", body);
return postJSON(CONFIG.baseUrl + "/api/ext/exports", body);
}
function queueExport() {
@@ -2646,7 +2825,7 @@ var HANDLE_FRAMES = 8;
return;
}
response = postJSON(BASE_URL + "/api/ext/exports/" + exportInfo.id + "/retry",
response = postJSON(CONFIG.baseUrl + "/api/ext/exports/" + exportInfo.id + "/retry",
{ note: "Retry from AE panel" });
if (response.status === 200) {
setPipelineStatus(shotCode + " " + exportInfo.versionString + " — requeued");
@@ -2703,24 +2882,42 @@ var HANDLE_FRAMES = 8;
var statusPanel;
win.orientation = "column";
win.alignChildren = ["fill", "top"];
win.spacing = 8;
win.margins = 12;
win.alignChildren = ["fill", "fill"];
win.spacing = 0;
win.margins = 0;
bannerFile = new File(BANNER_IMAGE_PATH);
var scrollRow = win.add("group");
scrollRow.orientation = "row";
scrollRow.alignChildren = ["fill", "fill"];
scrollRow.spacing = 0;
scrollRow.margins = 0;
var scrollContent = scrollRow.add("group");
scrollContent.orientation = "column";
scrollContent.alignChildren = ["fill", "top"];
scrollContent.spacing = 8;
scrollContent.margins = 12;
var vScrollbar = scrollRow.add("scrollbar");
vScrollbar.orientation = "vertical";
vScrollbar.preferredSize = [14, -1];
vScrollbar.minvalue = 0;
vScrollbar.value = 0;
bannerFile = new File(CONFIG.bannerImagePath);
if (bannerFile.exists) {
try {
bannerImage = win.add("image", undefined, bannerFile);
bannerImage = scrollContent.add("image", undefined, bannerFile);
bannerImage.alignment = ["fit", "top"];
bannerImage.size = [360, 80];
} catch (bannerError) {
}
}
title = win.add("statictext", undefined, "VFXReview Connector");
title = scrollContent.add("statictext", undefined, "VFXReview Connector");
title.alignment = ["fill", "top"];
shotBuilderPanel = win.add("panel", undefined, "Shot Builder");
shotBuilderPanel = scrollContent.add("panel", undefined, "Shot Builder");
shotBuilderPanel.orientation = "column";
shotBuilderPanel.alignChildren = ["fill", "top"];
shotBuilderPanel.margins = 10;
@@ -2730,13 +2927,33 @@ var HANDLE_FRAMES = 8;
shotBuilderGroup.alignChildren = ["fill", "center"];
shotBuilderGroup.spacing = 6;
uiState.episodeDropdown = shotBuilderGroup.add("dropdownlist", undefined, []);
uiState.episodeDropdown.preferredSize = [110, 24];
uiState.shotDropdown = shotBuilderGroup.add("dropdownlist", undefined, []);
uiState.shotDropdown.preferredSize = [150, 24];
shotBuilderButton = shotBuilderGroup.add("button", undefined, "Build Shot");
// Project row
var projectRow = shotBuilderPanel.add("group");
projectRow.orientation = "row";
projectRow.alignChildren = ["fill", "center"];
projectRow.spacing = 6;
var projectLabel = projectRow.add("statictext", undefined, "Project:");
projectLabel.preferredSize = [42, 20];
uiState.projectDropdown = projectRow.add("dropdownlist", undefined, []);
uiState.projectDropdown.preferredSize = [220, 24];
var settingsBtn = projectRow.add("button", undefined, "\u2699");
settingsBtn.preferredSize = [26, 24];
settingsBtn.helpTip = "Settings";
pipelinePanel = win.add("panel", undefined, "Render Pipeline");
// Episode / Scene row
var episodeRow = shotBuilderPanel.add("group");
episodeRow.orientation = "row";
episodeRow.alignChildren = ["fill", "center"];
episodeRow.spacing = 6;
uiState.episodeLabel = episodeRow.add("statictext", undefined, "Ep:");
uiState.episodeLabel.preferredSize = [42, 20];
uiState.episodeDropdown = episodeRow.add("dropdownlist", undefined, []);
uiState.episodeDropdown.preferredSize = [110, 24];
uiState.shotDropdown = episodeRow.add("dropdownlist", undefined, []);
uiState.shotDropdown.preferredSize = [130, 24];
shotBuilderButton = episodeRow.add("button", undefined, "Build Shot");
pipelinePanel = scrollContent.add("panel", undefined, "Render Pipeline");
pipelinePanel.orientation = "column";
pipelinePanel.alignChildren = ["fill", "top"];
pipelinePanel.margins = 8;
@@ -2767,7 +2984,7 @@ var HANDLE_FRAMES = 8;
refreshExportButton = pipelineRow2.add("button", undefined, "Refresh Status");
retryExportButton = pipelineRow2.add("button", undefined, "Retry Export");
buttonsPanel = win.add("panel", undefined, "Actions");
buttonsPanel = scrollContent.add("panel", undefined, "Actions");
buttonsPanel.orientation = "column";
buttonsPanel.alignChildren = ["fill", "top"];
buttonsPanel.margins = 8;
@@ -2837,21 +3054,21 @@ var HANDLE_FRAMES = 8;
btnRow7.spacing = 6;
incrementVersionButton = btnRow7.add("button", undefined, "Increment Shot Version");
workspacePanel = win.add("panel", undefined, "Workspace");
workspacePanel = scrollContent.add("panel", undefined, "Workspace");
workspacePanel.orientation = "column";
workspacePanel.alignChildren = ["fill", "top"];
workspacePanel.margins = 8;
workspacePanel.spacing = 4;
initWorkspaceButton = workspacePanel.add("button", undefined, "Initialise Workspace");
colorSpacePanel = win.add("panel", undefined, "Color Space");
colorSpacePanel = scrollContent.add("panel", undefined, "Color Space");
colorSpacePanel.orientation = "column";
colorSpacePanel.alignChildren = ["fill", "top"];
colorSpacePanel.margins = 8;
colorSpacePanel.spacing = 4;
ocioButton = colorSpacePanel.add("button", undefined, "Add OCIO to Footage Layers");
statusPanel = win.add("panel", undefined, "Status");
statusPanel = scrollContent.add("panel", undefined, "Status");
statusPanel.orientation = "column";
statusPanel.alignChildren = ["fill", "top"];
statusPanel.margins = 10;
@@ -2859,7 +3076,26 @@ var HANDLE_FRAMES = 8;
uiState.selectionText = statusPanel.add("statictext", undefined, "0 comps selected");
uiState.lastActionText = statusPanel.add("statictext", undefined, "Ready");
uiState.projectDropdown.onChange = function () {
if (uiState.projectDropdown.selection) {
CURRENT_PROJECT = uiState.projectDropdown.selection.projectData;
if (CURRENT_PROJECT) {
CONFIG.lastProjectCode = CURRENT_PROJECT.code;
saveConfig();
}
populateEpisodeOrSceneDropdown();
}
};
uiState.episodeDropdown.onChange = populateShotDropdown;
// ScriptUI silently swallows exceptions thrown inside onClick handlers.
settingsBtn.onClick = function () {
try {
openSettingsDialog();
} catch (settingsError) {
alert("VFXReview Connector \u2014 Settings error:\n" + settingsError.toString() +
(settingsError.line ? "\nLine " + settingsError.line : ""));
}
};
shotBuilderButton.onClick = buildShotFromDropdown;
queueExportButton.onClick = queueExport;
refreshExportButton.onClick = refreshExportStatus;
@@ -2883,8 +3119,24 @@ var HANDLE_FRAMES = 8;
win.layout.layout(true);
win.layout.resize();
var _contentH = scrollContent.size[1];
vScrollbar.maxvalue = Math.max(0, _contentH - scrollRow.size[1]);
vScrollbar.stepdelta = 20;
vScrollbar.jumpdelta = 80;
vScrollbar.onChanging = function () {
scrollContent.location.y = -Math.round(vScrollbar.value);
};
win.onResizing = win.onResize = function () {
this.layout.resize();
var visible = scrollRow.size[1];
vScrollbar.maxvalue = Math.max(0, _contentH - visible);
if (vScrollbar.value > vScrollbar.maxvalue) {
vScrollbar.value = vScrollbar.maxvalue;
}
scrollContent.location.y = -Math.round(vScrollbar.value);
};
return win;
@@ -2893,9 +3145,10 @@ var HANDLE_FRAMES = 8;
// app.scheduleTask evaluates in global scope — expose the poll tick there
$.global.__vfxrExportPollTick = pollExportTick;
loadConfig();
var panel = buildUI(thisObj);
refreshSelection();
populateEpisodeDropdown();
populateProjectDropdown();
if (panel instanceof Window) {
panel.center();