feat: live sync linked director controls
This commit is contained in:
@@ -11443,6 +11443,66 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const self = this;
|
||||
this._resolveLinkedInputValue = function (inputName, options = {}) {
|
||||
const graph = app.graph;
|
||||
const visited = new Set();
|
||||
const readNodeOutputValue = (node, slotIndex) => {
|
||||
if (!node) return null;
|
||||
|
||||
if (Array.isArray(node.widgets)) {
|
||||
for (const widget of node.widgets) {
|
||||
const value = widget?.value;
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(node.outputs) && slotIndex != null && slotIndex >= 0) {
|
||||
const outputName = node.outputs[slotIndex]?.name;
|
||||
if (outputName && Array.isArray(node.widgets)) {
|
||||
const namedWidget = node.widgets.find(w => w?.name === outputName);
|
||||
const value = namedWidget?.value;
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
const walkLink = (linkId) => {
|
||||
if (!graph || linkId == null) return null;
|
||||
if (visited.has(linkId)) return null;
|
||||
visited.add(linkId);
|
||||
|
||||
const link = graph.links?.[linkId];
|
||||
if (!link) return null;
|
||||
|
||||
const originNode = graph.getNodeById?.(link.origin_id);
|
||||
if (!originNode) return null;
|
||||
|
||||
const comfyClass = originNode.comfyClass || originNode.type || "";
|
||||
if (/reroute/i.test(comfyClass)) {
|
||||
const upstreamLink = originNode.inputs?.[0]?.link;
|
||||
if (upstreamLink != null) {
|
||||
return walkLink(upstreamLink);
|
||||
}
|
||||
}
|
||||
|
||||
return readNodeOutputValue(originNode, link.origin_slot);
|
||||
};
|
||||
|
||||
const input = self.inputs?.find(i => i.name === inputName);
|
||||
const value = input?.link != null ? walkLink(input.link) : null;
|
||||
if (value == null || !Number.isFinite(value)) return null;
|
||||
|
||||
if (options.integer) {
|
||||
return Math.round(value);
|
||||
}
|
||||
const decimals = Number.isInteger(options.decimals) ? options.decimals : 3;
|
||||
return parseFloat(value.toFixed(decimals));
|
||||
};
|
||||
this._syncGlobalPromptFromLink = function () {
|
||||
const globalInput = self.inputs?.find(i => i.name === "global_prompt");
|
||||
if (globalInput && globalInput.link !== null && globalInput.link !== undefined) {
|
||||
@@ -11497,6 +11557,51 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
};
|
||||
this._syncTimingFromLinks = function () {
|
||||
const linkedStart = self._resolveLinkedInputValue("start", { decimals: 3 });
|
||||
const linkedEnd = self._resolveLinkedInputValue("end", { decimals: 3 });
|
||||
const linkedDuration = self._resolveLinkedInputValue("duration", { decimals: 3 });
|
||||
const snapshot = `${linkedStart ?? ""}|${linkedEnd ?? ""}|${linkedDuration ?? ""}`;
|
||||
|
||||
if (snapshot === self._lastLinkedTimingSnapshot) {
|
||||
return;
|
||||
}
|
||||
self._lastLinkedTimingSnapshot = snapshot;
|
||||
|
||||
let changed = false;
|
||||
const applyWidget = (name, value) => {
|
||||
if (value == null) return;
|
||||
const widget = self.widgets?.find(w => w.name === name);
|
||||
if (!widget) return;
|
||||
if (widget.value === value) return;
|
||||
widget.value = value;
|
||||
if (widget.callback) {
|
||||
try {
|
||||
widget.callback(value);
|
||||
} catch (e) {}
|
||||
}
|
||||
changed = true;
|
||||
};
|
||||
|
||||
applyWidget("start_second", linkedStart);
|
||||
if (linkedDuration != null) {
|
||||
applyWidget("duration_seconds", Math.max(0.1, linkedDuration));
|
||||
} else {
|
||||
applyWidget("end_second", linkedEnd);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
if (self._ltxSettingsRefresh) {
|
||||
try { self._ltxSettingsRefresh(); } catch (e) {}
|
||||
}
|
||||
if (self._timelineEditor) {
|
||||
self._timelineEditor.render();
|
||||
}
|
||||
if (self.setDirtyCanvas) {
|
||||
self.setDirtyCanvas(true, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const origOnConnectionsChange = this.onConnectionsChange;
|
||||
this.onConnectionsChange = function (type, index, connected, link_info) {
|
||||
@@ -11504,6 +11609,8 @@ app.registerExtension({
|
||||
origOnConnectionsChange.apply(this, arguments);
|
||||
}
|
||||
self._syncGlobalPromptFromLink();
|
||||
self._syncTimingFromLinks();
|
||||
self._syncResolutionFromLinks?.();
|
||||
};
|
||||
|
||||
const origOnDrawForeground = this.onDrawForeground;
|
||||
@@ -11512,6 +11619,8 @@ app.registerExtension({
|
||||
origOnDrawForeground.apply(this, arguments);
|
||||
}
|
||||
self._syncGlobalPromptFromLink();
|
||||
self._syncTimingFromLinks();
|
||||
self._syncResolutionFromLinks?.();
|
||||
};
|
||||
|
||||
// --- LTX Director settings panel (Stage 1: Resolution | Timing/Reference) ---
|
||||
@@ -11648,6 +11757,65 @@ app.registerExtension({
|
||||
|
||||
syncPreset(); syncFps();
|
||||
|
||||
const refreshResolutionInputLocks = () => {
|
||||
const widthLinked = !!node.inputs?.find(i => i.name === "custom_width")?.link;
|
||||
const heightLinked = !!node.inputs?.find(i => i.name === "custom_height")?.link;
|
||||
const fpsLinked = !!node.inputs?.find(i => i.name === "frame_rate")?.link;
|
||||
const presetLinked = widthLinked || heightLinked;
|
||||
|
||||
widthIn.disabled = widthLinked;
|
||||
heightIn.disabled = heightLinked;
|
||||
presetSel.disabled = presetLinked;
|
||||
fpsIn.disabled = fpsLinked;
|
||||
fpsSel.disabled = fpsLinked;
|
||||
};
|
||||
|
||||
node._syncResolutionFromLinks = () => {
|
||||
const linkedWidth = node._resolveLinkedInputValue?.("custom_width", { integer: true });
|
||||
const linkedHeight = node._resolveLinkedInputValue?.("custom_height", { integer: true });
|
||||
const linkedFps = node._resolveLinkedInputValue?.("frame_rate", { integer: true });
|
||||
const snapshot = `${linkedWidth ?? ""}|${linkedHeight ?? ""}|${linkedFps ?? ""}`;
|
||||
|
||||
refreshResolutionInputLocks();
|
||||
|
||||
if (snapshot === node._lastLinkedResolutionSnapshot) {
|
||||
return;
|
||||
}
|
||||
node._lastLinkedResolutionSnapshot = snapshot;
|
||||
|
||||
let changed = false;
|
||||
|
||||
const widthWidget = getW("custom_width");
|
||||
if (linkedWidth != null && widthWidget && widthWidget.value !== linkedWidth) {
|
||||
widthWidget.value = linkedWidth;
|
||||
if (widthWidget.callback) {
|
||||
try { widthWidget.callback(linkedWidth); } catch (e) {}
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
const heightWidget = getW("custom_height");
|
||||
if (linkedHeight != null && heightWidget && heightWidget.value !== linkedHeight) {
|
||||
heightWidget.value = linkedHeight;
|
||||
if (heightWidget.callback) {
|
||||
try { heightWidget.callback(linkedHeight); } catch (e) {}
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (linkedFps != null) {
|
||||
const currentFps = parseInt(getW("frame_rate")?.value) || 24;
|
||||
if (currentFps !== linkedFps) {
|
||||
applyFrameRate(Math.max(1, linkedFps));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed && node._ltxSettingsRefresh) {
|
||||
try { node._ltxSettingsRefresh(); } catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
// ---- shared seconds<->frames timing infrastructure ----
|
||||
const TIME_NAMES = ["start_second", "end_second", "duration_seconds", "start_frame", "end_frame", "duration_frames"];
|
||||
const timeMode = () => { const dm = getW("display_mode"); return (dm && dm.value === "frames") ? "frames" : "seconds"; };
|
||||
@@ -11747,6 +11915,7 @@ app.registerExtension({
|
||||
if (rm && rm.value != null) rmSel.value = rm.value;
|
||||
if (rs) refIn.value = rs.value;
|
||||
syncPreset(); syncFps();
|
||||
refreshResolutionInputLocks();
|
||||
if (typeof unitSel !== "undefined" && unitSel) unitSel.value = timeMode();
|
||||
timeRefreshers.forEach(fn => fn());
|
||||
ensureTimingHidden();
|
||||
|
||||
Reference in New Issue
Block a user