@field/cascade 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +20 -3
- package/CHANGELOG.md +187 -0
- package/README.md +1 -2
- package/dist/assets/{CodeEditor-DSZBpzdy.js → CodeEditor-4O7FQ__R.js} +4 -4
- package/dist/assets/{CodePanel-sY6sZa7B.js → CodePanel-BU-autmq.js} +2 -2
- package/dist/assets/{CustomNodeDialog-BRKnqZhp.js → CustomNodeDialog-BRpDfOV5.js} +1 -1
- package/dist/assets/{OpenGraphDialog-ZsgL-iwr.js → OpenGraphDialog-BwbRlC0X.js} +1 -1
- package/dist/assets/{ProjectSettings-Cw17nNe3.js → ProjectSettings-DLOXLxcA.js} +1 -1
- package/dist/assets/{SettingsDialog-p25h3tj0.js → SettingsDialog-Bz8JxISH.js} +1 -1
- package/dist/assets/{VersionHistoryDialog-DzFLUotB.js → VersionHistoryDialog-D5Z0nJ5-.js} +1 -1
- package/dist/assets/{core-BICrEt0y.js → core-Yqi0Ni61.js} +1 -1
- package/dist/assets/{cssMode-D7TXw57I.js → cssMode-CxUzc4vp.js} +1 -1
- package/dist/assets/{definitions-DUww2Rt-.js → definitions-GreEMXLV.js} +1 -1
- package/dist/assets/{editor.api-DvkCO3ko.js → editor.api-BNFb25CX.js} +1 -1
- package/dist/assets/{freemarker2-BA2lxsF1.js → freemarker2-0ZDnWxeM.js} +1 -1
- package/dist/assets/geo-u0n_jOsD.js +1 -0
- package/dist/assets/{handlebars-BcDiyPd8.js → handlebars-Cy18MoaD.js} +1 -1
- package/dist/assets/{html-C7O4TyhX.js → html-B-2Dx7nq.js} +1 -1
- package/dist/assets/{htmlMode-WaDGF3Mj.js → htmlMode-DUAc9B_d.js} +1 -1
- package/dist/assets/{image-DR4oeyJB.js → image-cAa-Nu9O.js} +1 -1
- package/dist/assets/{index-DyvDC6pS.js → index-5ZtDAuy2.js} +42 -41
- package/dist/assets/{javascript-CpIEK7tz.js → javascript-DeQsDqP5.js} +1 -1
- package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-BiFwJVSk.js} +1 -1
- package/dist/assets/{liquid-CN6FFHZE.js → liquid-BLoHH68a.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-CNFgm1Kv.js} +1 -1
- package/dist/assets/math-DioIQOPJ.js +1 -0
- package/dist/assets/{mdx-DE89pafV.js → mdx-CFXQVNfs.js} +1 -1
- package/dist/assets/pop-DC_TQG9t.js +1 -0
- package/dist/assets/{python-ByTllTB9.js → python-Cf7S7p2E.js} +1 -1
- package/dist/assets/{razor-USYlRRCE.js → razor-CMHBcG-g.js} +1 -1
- package/dist/assets/{tsMode-BXpN8iUz.js → tsMode-BEih0GDj.js} +1 -1
- package/dist/assets/{typescript-DInBjGQQ.js → typescript-COEKYD_a.js} +1 -1
- package/dist/assets/{workers-C7Ye5B7D.js → workers-BNhAhMNn.js} +1 -1
- package/dist/assets/{xml-U0Uocqyo.js → xml-CPiMpnPl.js} +1 -1
- package/dist/assets/{yaml-CBw_PVYf.js → yaml-Bgp1ITLR.js} +1 -1
- package/dist/cli/Graph-ZJJTRQTM.js +2975 -0
- package/dist/cli/chunk-2KXB47FP.js +115 -0
- package/dist/cli/chunk-35D343DC.js +1447 -0
- package/dist/cli/chunk-36P5K3SD.js +631 -0
- package/dist/cli/chunk-3JTJHF7V.js +267 -0
- package/dist/cli/chunk-4UDFYF5G.js +132 -0
- package/dist/cli/chunk-53NMBJAO.js +35 -0
- package/dist/cli/chunk-A4OAMOZH.js +310 -0
- package/dist/cli/chunk-BIJO5OFS.js +71 -0
- package/dist/cli/chunk-BP7UKF36.js +376 -0
- package/dist/cli/chunk-CKS2FQWR.js +89 -0
- package/dist/cli/chunk-CO22XHNG.js +593 -0
- package/dist/cli/chunk-D6BUOHUG.js +634 -0
- package/dist/cli/chunk-DEQSEFFH.js +153 -0
- package/dist/cli/chunk-DUIUFVU2.js +241 -0
- package/dist/cli/chunk-EUIAC3U3.js +3006 -0
- package/dist/cli/chunk-FCDXJ6EK.js +2208 -0
- package/dist/cli/chunk-GFS6W3C5.js +308 -0
- package/dist/cli/chunk-HFHWYL55.js +139 -0
- package/dist/cli/chunk-HSNOBIVT.js +263 -0
- package/dist/cli/chunk-ICH7N5PN.js +26 -0
- package/dist/cli/chunk-J7VK7MNF.js +418 -0
- package/dist/cli/chunk-KH4MUBKF.js +1551 -0
- package/dist/cli/chunk-MIIEFUGG.js +623 -0
- package/dist/cli/chunk-OWWBYLHC.js +136 -0
- package/dist/cli/chunk-POMMNKZZ.js +672 -0
- package/dist/cli/chunk-QBBKRNGY.js +90 -0
- package/dist/cli/chunk-QQPA2ZHH.js +273 -0
- package/dist/cli/chunk-RLCAJBVR.js +224 -0
- package/dist/cli/chunk-RQZ4SX5R.js +1125 -0
- package/dist/cli/chunk-RUU3YWXU.js +99 -0
- package/dist/cli/chunk-RXDIMXN5.js +98 -0
- package/dist/cli/chunk-SGJAET2L.js +197 -0
- package/dist/cli/chunk-VVGSLVSF.js +151 -0
- package/dist/cli/chunk-WD62JIP6.js +118 -0
- package/dist/cli/chunk-WIH4TEIS.js +104 -0
- package/dist/cli/chunk-WKURDVYP.js +1119 -0
- package/dist/cli/chunk-WPUDGEFY.js +20 -0
- package/dist/cli/chunk-XWDSKG7N.js +1301 -0
- package/dist/cli/chunk-YAT5Y25U.js +121 -0
- package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
- package/dist/cli/cliCommands-UQSFGEHF.js +3464 -0
- package/dist/cli/commands-Q23M26QM.js +1292 -0
- package/dist/cli/definitions-HF2FTHYU.js +32 -0
- package/dist/cli/doctorCommand-B7BPQMN6.js +76 -0
- package/dist/cli/edit-JHCPTQFT.js +28 -0
- package/dist/cli/editorCommands-UIF5YLUZ.js +33 -0
- package/dist/cli/encode-HAUZTYPT.js +17 -0
- package/dist/cli/frames-LSAUBZ7P.js +16 -0
- package/dist/cli/geo-4RNK3QVN.js +43 -0
- package/dist/cli/graphCommands-6BMAKGQE.js +184 -0
- package/dist/cli/graphFile-VPSQ6OF7.js +286 -0
- package/dist/cli/headlessCanvas-5XLDZT7M.js +15 -0
- package/dist/cli/helpCommand-3VRE3KVO.js +155 -0
- package/dist/cli/index.js +100 -30756
- package/dist/cli/math-ZRMLKU2D.js +16 -0
- package/dist/cli/mcp-ZLEEMYGF.js +164 -0
- package/dist/cli/parity-KINARCP2.js +328 -0
- package/dist/cli/pop-USDFXB7H.js +39 -0
- package/dist/cli/project-K3QIPGBB.js +12 -0
- package/dist/cli/projectDefinition-DMTZN2NE.js +27 -0
- package/dist/cli/projectRuntime-Z5PJOH47.js +48 -0
- package/dist/cli/registerStandardNodes-S2VLMF6J.js +3081 -0
- package/dist/cli/render-YURAPPUI.js +436 -0
- package/dist/cli/runCommand-PI764LEG.js +198 -0
- package/dist/cli/runner-RA6UKEPD.js +37 -0
- package/dist/cli/runtime/image.ts +277 -0
- package/dist/cli/runtime/stage.ts +4 -1
- package/dist/cli/stageRunner-KUNIMQKJ.js +10 -0
- package/dist/cli/verbs-RCE3K4VQ.js +19 -0
- package/dist/cli/webBuild-GT4PZT5D.js +37 -0
- package/dist/contracts/documentFormat.d.ts +18 -0
- package/dist/contracts/documentFormat.js +195 -0
- package/dist/contracts/editor.d.ts +17 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/image.d.ts +141 -0
- package/dist/image.js +127 -0
- package/dist/index.html +1 -1
- package/dist/player/editor.js +32 -7
- package/dist/runtime/builtins/headless.d.ts +19 -0
- package/dist/runtime/builtins/headless.js +25 -0
- package/dist/runtime/builtins/image/contactSheet.d.ts +226 -0
- package/dist/runtime/builtins/image/contactSheet.js +199 -0
- package/dist/runtime/builtins/image/index.d.ts +9 -0
- package/dist/runtime/builtins/image/index.js +9 -0
- package/dist/stage.js +4 -1
- package/doc/CLI_REFERENCE.md +73 -2
- package/doc/EDITOR_EXPORT.md +11 -2
- package/doc/MCP.md +17 -1
- package/doc/NODE_REFERENCE.md +142 -0
- package/doc/PROJECT_AUTHORING.md +127 -7
- package/package.json +16 -4
- package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
- package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
- package/dist/assets/geo-C2RHOVHC.js +0 -1
- package/dist/assets/math-DaDEgWKt.js +0 -1
- package/dist/assets/pop-pLH7L0PW.js +0 -1
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import {
|
|
2
|
+
expressionEngine
|
|
3
|
+
} from "./chunk-RUU3YWXU.js";
|
|
4
|
+
import {
|
|
5
|
+
imagePath,
|
|
6
|
+
sequenceFileName,
|
|
7
|
+
sequenceWidth
|
|
8
|
+
} from "./chunk-ICH7N5PN.js";
|
|
9
|
+
import {
|
|
10
|
+
TimeState,
|
|
11
|
+
runFrameRange
|
|
12
|
+
} from "./chunk-CO22XHNG.js";
|
|
13
|
+
|
|
14
|
+
// src/cli/frames.ts
|
|
15
|
+
import fs from "node:fs/promises";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
|
|
18
|
+
// src/engine/cascade.ts
|
|
19
|
+
var CascadeContext = class {
|
|
20
|
+
constructor() {
|
|
21
|
+
this._graph = null;
|
|
22
|
+
this._pwd = null;
|
|
23
|
+
/**
|
|
24
|
+
* Time state. Frames are stored as real numbers: `frame()` reports the
|
|
25
|
+
* integer frame ($F) and `fframe()` the fractional one ($FF), so sub-frame
|
|
26
|
+
* time exists rather than being floored away at the door.
|
|
27
|
+
*/
|
|
28
|
+
this._time = new TimeState();
|
|
29
|
+
this._selectedNodes = /* @__PURE__ */ new Set();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Initialize with a graph reference
|
|
33
|
+
*/
|
|
34
|
+
setGraph(graph) {
|
|
35
|
+
this._graph = graph;
|
|
36
|
+
expressionEngine.setGraph(graph);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the current graph
|
|
40
|
+
*/
|
|
41
|
+
getGraph() {
|
|
42
|
+
return this._graph;
|
|
43
|
+
}
|
|
44
|
+
// ============ Node Access ============
|
|
45
|
+
/**
|
|
46
|
+
* Find a node by path (absolute or relative to pwd)
|
|
47
|
+
*/
|
|
48
|
+
node(path2) {
|
|
49
|
+
if (!this._graph) return null;
|
|
50
|
+
if (path2.startsWith("/")) {
|
|
51
|
+
return this.resolveAbsolutePath(path2);
|
|
52
|
+
}
|
|
53
|
+
if (this._pwd) {
|
|
54
|
+
return this._pwd.node(path2);
|
|
55
|
+
}
|
|
56
|
+
return this.resolveAbsolutePath("/" + path2);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Find a parameter by path (e.g., "/node1/radius")
|
|
60
|
+
*/
|
|
61
|
+
parm(path2) {
|
|
62
|
+
const lastSlash = path2.lastIndexOf("/");
|
|
63
|
+
if (lastSlash === -1) return null;
|
|
64
|
+
const nodePath = path2.substring(0, lastSlash) || "/";
|
|
65
|
+
const parmName = path2.substring(lastSlash + 1);
|
|
66
|
+
const node = this.node(nodePath);
|
|
67
|
+
return node?.parm(parmName) ?? null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get the root node (virtual - returns null, represents "/")
|
|
71
|
+
* In Cascade, root is not a real node but the graph itself
|
|
72
|
+
*/
|
|
73
|
+
root() {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get current working directory/network
|
|
78
|
+
*/
|
|
79
|
+
pwd() {
|
|
80
|
+
return this._pwd;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Set current working directory/network
|
|
84
|
+
*/
|
|
85
|
+
setPwd(node) {
|
|
86
|
+
this._pwd = node;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Navigate to a path (like cd command)
|
|
90
|
+
*/
|
|
91
|
+
cd(path2) {
|
|
92
|
+
if (path2 === "/") {
|
|
93
|
+
this._pwd = null;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const target = this.node(path2);
|
|
97
|
+
if (target && target.isNetwork()) {
|
|
98
|
+
this._pwd = target;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// ============ Selection ============
|
|
102
|
+
/**
|
|
103
|
+
* Get all selected nodes
|
|
104
|
+
*/
|
|
105
|
+
selectedNodes() {
|
|
106
|
+
if (!this._graph) return [];
|
|
107
|
+
return Array.from(this._selectedNodes).map((id) => this._graph.getNode(id)).filter((n) => n !== null);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get selected node IDs
|
|
111
|
+
*/
|
|
112
|
+
selectedNodeIds() {
|
|
113
|
+
return Array.from(this._selectedNodes);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Set selection (internal use by editor)
|
|
117
|
+
*/
|
|
118
|
+
setSelection(nodeIds) {
|
|
119
|
+
this._selectedNodes = new Set(nodeIds);
|
|
120
|
+
this.onSelectionChange?.(nodeIds);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Clear all selection
|
|
124
|
+
*/
|
|
125
|
+
clearAllSelected() {
|
|
126
|
+
this._selectedNodes.clear();
|
|
127
|
+
this.onSelectionChange?.([]);
|
|
128
|
+
}
|
|
129
|
+
// ============ Time/Playback ============
|
|
130
|
+
/**
|
|
131
|
+
* Get current frame number (integer) — $F
|
|
132
|
+
*/
|
|
133
|
+
frame() {
|
|
134
|
+
return this._time.frame;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get current frame as a fractional value — $FF
|
|
138
|
+
*/
|
|
139
|
+
fframe() {
|
|
140
|
+
return this._time.frameFraction;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Get current time in seconds — $T
|
|
144
|
+
*/
|
|
145
|
+
time() {
|
|
146
|
+
return this._time.time;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get frames per second — $FPS
|
|
150
|
+
*/
|
|
151
|
+
fps() {
|
|
152
|
+
return this._time.fps;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Set current frame. Fractional frames are preserved; `frame()` still
|
|
156
|
+
* reports the floored integer frame.
|
|
157
|
+
*/
|
|
158
|
+
setFrame(frame) {
|
|
159
|
+
this._time.setFrame(frame);
|
|
160
|
+
expressionEngine.setFrame(this._time.frameFraction);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Set current time in seconds, keeping the sub-frame remainder
|
|
164
|
+
*/
|
|
165
|
+
setTime(time) {
|
|
166
|
+
this._time.setTime(time);
|
|
167
|
+
expressionEngine.setFrame(this._time.frameFraction);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Set FPS
|
|
171
|
+
*/
|
|
172
|
+
setFps(fps) {
|
|
173
|
+
this._time.setFps(fps);
|
|
174
|
+
expressionEngine.setFps(this._time.fps);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The clock a frame-range loop drives. Nothing here reads a wall clock —
|
|
178
|
+
* the caller owns time, which is what keeps a cook deterministic.
|
|
179
|
+
*/
|
|
180
|
+
clock() {
|
|
181
|
+
return {
|
|
182
|
+
setFrame: (frame) => this.setFrame(frame),
|
|
183
|
+
markTimeDependentDirty: () => this.markTimeDependentDirty(),
|
|
184
|
+
getFrame: () => this.frame(),
|
|
185
|
+
getFrameFraction: () => this.fframe(),
|
|
186
|
+
getTime: () => this.time(),
|
|
187
|
+
getFps: () => this.fps()
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// ============ Channel Shortcuts ============
|
|
191
|
+
/**
|
|
192
|
+
* Get parameter value as number (shorthand)
|
|
193
|
+
*/
|
|
194
|
+
ch(path2) {
|
|
195
|
+
const p = this.parm(path2);
|
|
196
|
+
return p?.evalAsFloat() ?? 0;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get parameter value as string (shorthand)
|
|
200
|
+
*/
|
|
201
|
+
chs(path2) {
|
|
202
|
+
const p = this.parm(path2);
|
|
203
|
+
return p?.evalAsString() ?? "";
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get parameter value as vector (shorthand)
|
|
207
|
+
*/
|
|
208
|
+
chv(path2) {
|
|
209
|
+
const p = this.parm(path2);
|
|
210
|
+
const val = p?.eval();
|
|
211
|
+
if (Array.isArray(val)) return val;
|
|
212
|
+
if (typeof val === "number") return [val];
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
// ============ Time-Dependent Updates ============
|
|
216
|
+
/**
|
|
217
|
+
* Mark all time-dependent nodes as dirty
|
|
218
|
+
* Call this at the start of each frame in the animation loop
|
|
219
|
+
*/
|
|
220
|
+
markTimeDependentDirty() {
|
|
221
|
+
if (!this._graph) return;
|
|
222
|
+
for (const node of this._graph.nodes) {
|
|
223
|
+
if (node.isTimeDependent) {
|
|
224
|
+
node.markDirty();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get all time-dependent nodes in the graph
|
|
230
|
+
*/
|
|
231
|
+
getTimeDependentNodes() {
|
|
232
|
+
if (!this._graph) return [];
|
|
233
|
+
return this._graph.nodes.filter((n) => n.isTimeDependent);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Advance the frame and mark time-dependent nodes dirty.
|
|
237
|
+
* Convenience method for animation loops; `step` may be fractional.
|
|
238
|
+
* Returns the new fractional frame.
|
|
239
|
+
*/
|
|
240
|
+
nextFrame(step = 1) {
|
|
241
|
+
this.setFrame(this.fframe() + step);
|
|
242
|
+
this.markTimeDependentDirty();
|
|
243
|
+
return this.fframe();
|
|
244
|
+
}
|
|
245
|
+
// ============ Internal Helpers ============
|
|
246
|
+
resolveAbsolutePath(path2) {
|
|
247
|
+
if (!this._graph) return null;
|
|
248
|
+
const segments = path2.replace(/^\//, "").split("/").filter((s) => s.length > 0);
|
|
249
|
+
if (segments.length === 0) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
let current = null;
|
|
253
|
+
const rootNodes = this._graph.nodes.filter((n) => !n.parent);
|
|
254
|
+
for (let i = 0; i < segments.length; i++) {
|
|
255
|
+
const segment = segments[i];
|
|
256
|
+
const searchIn = current ? current.children() : rootNodes;
|
|
257
|
+
const found = searchIn.find((n) => n.id === segment);
|
|
258
|
+
if (!found) return null;
|
|
259
|
+
current = found;
|
|
260
|
+
}
|
|
261
|
+
return current;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
var cascade = new CascadeContext();
|
|
265
|
+
|
|
266
|
+
// src/engine/animation/frameRange.ts
|
|
267
|
+
async function evaluateFrameRange(options) {
|
|
268
|
+
const { fps, context = cascade, ...rest } = options;
|
|
269
|
+
const previousFps = context.fps();
|
|
270
|
+
if (fps !== void 0) context.setFps(fps);
|
|
271
|
+
try {
|
|
272
|
+
return await runFrameRange(context.clock(), rest);
|
|
273
|
+
} finally {
|
|
274
|
+
if (fps !== void 0) context.setFps(previousFps);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/cli/frames.ts
|
|
279
|
+
function parseFrameSpec(spec) {
|
|
280
|
+
const text = String(spec ?? "").trim();
|
|
281
|
+
const match = /^(-?\d+(?:\.\d+)?)(?:\s*[-:]\s*(-?\d+(?:\.\d+)?))?(?:\s*x\s*(\d+(?:\.\d+)?))?$/i.exec(text);
|
|
282
|
+
if (!match) {
|
|
283
|
+
throw new Error(`--frames expects a range like 1-100, 1-100x2, or a single frame like 42 (got "${spec}")`);
|
|
284
|
+
}
|
|
285
|
+
const start = Number(match[1]);
|
|
286
|
+
const end = match[2] === void 0 ? start : Number(match[2]);
|
|
287
|
+
const step = match[3] === void 0 ? void 0 : Number(match[3]);
|
|
288
|
+
if (end < start) throw new Error(`--frames: end (${end}) must not be before start (${start})`);
|
|
289
|
+
if (step !== void 0 && !(step > 0)) throw new Error(`--frames: step must be greater than 0 (got ${step})`);
|
|
290
|
+
return step === void 0 ? { start, end } : { start, end, step };
|
|
291
|
+
}
|
|
292
|
+
function outputPorts(graph, entryNode) {
|
|
293
|
+
const nodes = entryNode ? [entryNode] : graph.nodes;
|
|
294
|
+
const found = [];
|
|
295
|
+
for (const node of nodes) {
|
|
296
|
+
for (const port of node.outputs) {
|
|
297
|
+
if (port.dataType !== "image") continue;
|
|
298
|
+
if (!entryNode && port.connections.length > 0) continue;
|
|
299
|
+
found.push({ node, port });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return found;
|
|
303
|
+
}
|
|
304
|
+
function outputSnapshot(graph) {
|
|
305
|
+
return JSON.stringify(graph.nodes.map((node) => [node.id, node.outputs.map((port) => port.value ?? null)]));
|
|
306
|
+
}
|
|
307
|
+
async function cookUntilSettled(graph, entryNode, options = {}) {
|
|
308
|
+
const { fixpoint = false, passes = 8 } = options;
|
|
309
|
+
const dirtyAll = () => {
|
|
310
|
+
for (const node of graph.nodes) node.markDirty?.();
|
|
311
|
+
};
|
|
312
|
+
let previous = fixpoint ? outputSnapshot(graph) : null;
|
|
313
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
314
|
+
await graph.execute(entryNode);
|
|
315
|
+
await graph.scheduler?.whenIdle?.();
|
|
316
|
+
if (graph.nodes.some((node) => node.error !== null)) return;
|
|
317
|
+
const stale = graph.nodes.some((node) => node.cookState === "stale" || node.cookState === "queued");
|
|
318
|
+
if (!fixpoint) {
|
|
319
|
+
if (!stale) return;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const snapshot = outputSnapshot(graph);
|
|
323
|
+
const settled = !stale && snapshot === previous;
|
|
324
|
+
previous = snapshot;
|
|
325
|
+
if (settled) return;
|
|
326
|
+
dirtyAll();
|
|
327
|
+
}
|
|
328
|
+
throw new Error(`graph did not settle after ${passes} cook passes`);
|
|
329
|
+
}
|
|
330
|
+
async function renderFrameRange(options) {
|
|
331
|
+
const { graph, project, out, start, end, step, fps, entryNode, verbose, signal } = options;
|
|
332
|
+
cascade.setGraph(graph);
|
|
333
|
+
const outDirectory = project.resolve(out);
|
|
334
|
+
await fs.mkdir(outDirectory, { recursive: true });
|
|
335
|
+
const files = [];
|
|
336
|
+
const width = sequenceWidth(end);
|
|
337
|
+
const result = await evaluateFrameRange({
|
|
338
|
+
start,
|
|
339
|
+
end,
|
|
340
|
+
step,
|
|
341
|
+
fps,
|
|
342
|
+
signal,
|
|
343
|
+
onFrame: async ({ frame, index, total }) => {
|
|
344
|
+
if (index === 0) for (const node of graph.nodes) node.markDirty();
|
|
345
|
+
await cookUntilSettled(graph, entryNode, { fixpoint: index === 0 });
|
|
346
|
+
const failed = graph.nodes.filter((node) => node.error !== null);
|
|
347
|
+
if (failed.length) {
|
|
348
|
+
const detail = failed.map((node) => `${node.id}: ${node.error?.message ?? "unknown cook error"}`).join("; ");
|
|
349
|
+
throw new Error(`frame ${frame} failed \u2014 ${detail}`);
|
|
350
|
+
}
|
|
351
|
+
const ports = outputPorts(graph, entryNode);
|
|
352
|
+
if (!ports.length) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
"No image output to save: the graph has no unconsumed image port. Name the node to render with --entry-node."
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
for (const { node, port } of ports) {
|
|
358
|
+
const source = imagePath(port.value);
|
|
359
|
+
if (!source) continue;
|
|
360
|
+
const name = sequenceFileName(node.id, frame, source, width);
|
|
361
|
+
await fs.copyFile(project.resolveMedia(source), path.join(outDirectory, name));
|
|
362
|
+
files.push(path.posix.join(out, name));
|
|
363
|
+
}
|
|
364
|
+
if (verbose) console.log(` frame ${frame} (${index + 1}/${total})`);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
return { frames: result.frames, files, aborted: result.aborted };
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export {
|
|
371
|
+
parseFrameSpec,
|
|
372
|
+
outputPorts,
|
|
373
|
+
cookUntilSettled,
|
|
374
|
+
renderFrameRange
|
|
375
|
+
};
|
|
376
|
+
//# sourceMappingURL=chunk-BP7UKF36.js.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/cli/jsonEnvelope.ts
|
|
2
|
+
var ExitRequested = class extends Error {
|
|
3
|
+
constructor(code) {
|
|
4
|
+
super("");
|
|
5
|
+
this.code = code;
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
async function captureEnvelope(command) {
|
|
9
|
+
const logged = [];
|
|
10
|
+
const warnings = [];
|
|
11
|
+
const errors = [];
|
|
12
|
+
const original = {
|
|
13
|
+
log: console.log,
|
|
14
|
+
info: console.info,
|
|
15
|
+
warn: console.warn,
|
|
16
|
+
error: console.error,
|
|
17
|
+
write: process.stdout.write,
|
|
18
|
+
exit: process.exit
|
|
19
|
+
};
|
|
20
|
+
const text = (args) => args.map((arg) => typeof arg === "string" ? arg : arg instanceof Error ? arg.message : JSON.stringify(arg)).join(" ");
|
|
21
|
+
console.log = (...args) => {
|
|
22
|
+
logged.push(text(args));
|
|
23
|
+
};
|
|
24
|
+
console.info = console.log;
|
|
25
|
+
console.warn = (...args) => {
|
|
26
|
+
warnings.push(text(args));
|
|
27
|
+
};
|
|
28
|
+
console.error = (...args) => {
|
|
29
|
+
errors.push(text(args));
|
|
30
|
+
};
|
|
31
|
+
process.stdout.write = ((chunk) => {
|
|
32
|
+
logged.push(String(chunk));
|
|
33
|
+
return true;
|
|
34
|
+
});
|
|
35
|
+
process.exit = ((code) => {
|
|
36
|
+
throw new ExitRequested(typeof code === "number" ? code : 0);
|
|
37
|
+
});
|
|
38
|
+
let result;
|
|
39
|
+
let failure = null;
|
|
40
|
+
try {
|
|
41
|
+
result = await command();
|
|
42
|
+
if (process.exitCode && process.exitCode !== 0) failure = errors.join("\n") || `exited with code ${process.exitCode}`;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error instanceof ExitRequested) {
|
|
45
|
+
if (error.code !== 0) failure = errors.join("\n") || `exited with code ${error.code}`;
|
|
46
|
+
} else {
|
|
47
|
+
failure = error instanceof Error ? error.message : String(error);
|
|
48
|
+
}
|
|
49
|
+
} finally {
|
|
50
|
+
console.log = original.log;
|
|
51
|
+
console.info = original.info;
|
|
52
|
+
console.warn = original.warn;
|
|
53
|
+
console.error = original.error;
|
|
54
|
+
process.stdout.write = original.write;
|
|
55
|
+
process.exit = original.exit;
|
|
56
|
+
}
|
|
57
|
+
const allWarnings = [...warnings, ...failure ? [] : errors].flatMap(splitLines);
|
|
58
|
+
if (failure) return { ok: false, error: { message: failure.replace(/^Error: /, "") }, warnings: allWarnings };
|
|
59
|
+
return { ok: true, data: result !== void 0 ? result : dataFromOutput(logged), warnings: allWarnings };
|
|
60
|
+
}
|
|
61
|
+
async function runWithJsonEnvelope(command) {
|
|
62
|
+
const envelope = await captureEnvelope(command);
|
|
63
|
+
process.stdout.write(`${JSON.stringify(envelope)}
|
|
64
|
+
`);
|
|
65
|
+
process.exitCode = envelope.ok ? 0 : 1;
|
|
66
|
+
}
|
|
67
|
+
function dataFromOutput(chunks) {
|
|
68
|
+
const output = chunks.join("\n").trim();
|
|
69
|
+
if (!output) return null;
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(output);
|
|
72
|
+
} catch {
|
|
73
|
+
const lines = output.split("\n").filter(Boolean);
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(lines[lines.length - 1]);
|
|
76
|
+
} catch {
|
|
77
|
+
return { output: lines };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function splitLines(text) {
|
|
82
|
+
return text.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
captureEnvelope,
|
|
87
|
+
runWithJsonEnvelope
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=chunk-CKS2FQWR.js.map
|