@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,593 @@
|
|
|
1
|
+
// packages/runtime/src/animation/frameRange.ts
|
|
2
|
+
function frameRange(start, end, step = 1) {
|
|
3
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
|
4
|
+
throw new Error(`frameRange: start and end must be finite (got ${start}, ${end})`);
|
|
5
|
+
}
|
|
6
|
+
if (!Number.isFinite(step) || step <= 0) {
|
|
7
|
+
throw new Error(`frameRange: step must be a finite number greater than 0 (got ${step})`);
|
|
8
|
+
}
|
|
9
|
+
if (end < start) {
|
|
10
|
+
throw new Error(`frameRange: end (${end}) must not be before start (${start})`);
|
|
11
|
+
}
|
|
12
|
+
const span = end - start;
|
|
13
|
+
const count = Math.floor(span / step + 1e-9) + 1;
|
|
14
|
+
const frames = new Array(count);
|
|
15
|
+
for (let i = 0; i < count; i++) frames[i] = start + i * step;
|
|
16
|
+
return frames;
|
|
17
|
+
}
|
|
18
|
+
async function runFrameRange(clock, options) {
|
|
19
|
+
const { start, end, step = 1, onFrame, signal, restoreFrame = true } = options;
|
|
20
|
+
const frames = frameRange(start, end, step);
|
|
21
|
+
const previousFrame = clock.getFrameFraction();
|
|
22
|
+
const visited = [];
|
|
23
|
+
let aborted = false;
|
|
24
|
+
try {
|
|
25
|
+
for (let index = 0; index < frames.length; index++) {
|
|
26
|
+
if (signal?.aborted) {
|
|
27
|
+
aborted = true;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
clock.setFrame(frames[index]);
|
|
31
|
+
clock.markTimeDependentDirty();
|
|
32
|
+
await onFrame({
|
|
33
|
+
index,
|
|
34
|
+
total: frames.length,
|
|
35
|
+
frame: clock.getFrame(),
|
|
36
|
+
fframe: clock.getFrameFraction(),
|
|
37
|
+
time: clock.getTime(),
|
|
38
|
+
fps: clock.getFps()
|
|
39
|
+
});
|
|
40
|
+
visited.push(clock.getFrameFraction());
|
|
41
|
+
}
|
|
42
|
+
} finally {
|
|
43
|
+
if (restoreFrame) {
|
|
44
|
+
clock.setFrame(previousFrame);
|
|
45
|
+
clock.markTimeDependentDirty();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { frames: visited, frameCount: visited.length, aborted };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// packages/runtime/src/animation/channel.ts
|
|
52
|
+
var DEFAULT_INTERPOLATION = "smooth";
|
|
53
|
+
var INTERPOLATIONS = ["constant", "linear", "smooth"];
|
|
54
|
+
function isInterpolation(value) {
|
|
55
|
+
return typeof value === "string" && INTERPOLATIONS.includes(value);
|
|
56
|
+
}
|
|
57
|
+
var EMPTY_CHANNEL = Object.freeze({ keys: Object.freeze([]) });
|
|
58
|
+
function createChannel(keys = []) {
|
|
59
|
+
const byFrame = /* @__PURE__ */ new Map();
|
|
60
|
+
for (const key of keys) {
|
|
61
|
+
if (!key) continue;
|
|
62
|
+
const frame = Number(key.frame);
|
|
63
|
+
const value = Number(key.value);
|
|
64
|
+
if (!Number.isFinite(frame) || !Number.isFinite(value)) continue;
|
|
65
|
+
byFrame.set(frame, {
|
|
66
|
+
frame,
|
|
67
|
+
value,
|
|
68
|
+
interpolation: isInterpolation(key.interpolation) ? key.interpolation : DEFAULT_INTERPOLATION
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const sorted = [...byFrame.values()].sort((a, b) => a.frame - b.frame);
|
|
72
|
+
return { keys: sorted };
|
|
73
|
+
}
|
|
74
|
+
function isEmptyChannel(channel) {
|
|
75
|
+
return !channel || channel.keys.length === 0;
|
|
76
|
+
}
|
|
77
|
+
function keyAt(channel, frame) {
|
|
78
|
+
return channel.keys.find((key) => key.frame === frame);
|
|
79
|
+
}
|
|
80
|
+
function setKey(channel, frame, value, interpolation) {
|
|
81
|
+
const base = channel ?? EMPTY_CHANNEL;
|
|
82
|
+
if (!Number.isFinite(frame) || !Number.isFinite(value)) return createChannel(base.keys);
|
|
83
|
+
const existing = keyAt(base, frame);
|
|
84
|
+
const mode = interpolation ?? existing?.interpolation ?? DEFAULT_INTERPOLATION;
|
|
85
|
+
const kept = base.keys.filter((key) => key.frame !== frame);
|
|
86
|
+
return createChannel([...kept, { frame, value, interpolation: mode }]);
|
|
87
|
+
}
|
|
88
|
+
function deleteKey(channel, frame) {
|
|
89
|
+
const base = channel ?? EMPTY_CHANNEL;
|
|
90
|
+
return createChannel(base.keys.filter((key) => key.frame !== frame));
|
|
91
|
+
}
|
|
92
|
+
function sampleChannel(channel, frame) {
|
|
93
|
+
if (!channel || channel.keys.length === 0) return void 0;
|
|
94
|
+
const keys = channel.keys;
|
|
95
|
+
if (!Number.isFinite(frame)) return keys[0].value;
|
|
96
|
+
if (keys.length === 1) return keys[0].value;
|
|
97
|
+
if (frame <= keys[0].frame) return keys[0].value;
|
|
98
|
+
if (frame >= keys[keys.length - 1].frame) return keys[keys.length - 1].value;
|
|
99
|
+
const i = segmentIndex(keys, frame);
|
|
100
|
+
const k0 = keys[i];
|
|
101
|
+
const k1 = keys[i + 1];
|
|
102
|
+
const span = k1.frame - k0.frame;
|
|
103
|
+
if (span <= 0) return k1.value;
|
|
104
|
+
const t = (frame - k0.frame) / span;
|
|
105
|
+
switch (k0.interpolation) {
|
|
106
|
+
case "constant":
|
|
107
|
+
return k0.value;
|
|
108
|
+
case "linear":
|
|
109
|
+
return k0.value + (k1.value - k0.value) * t;
|
|
110
|
+
case "smooth":
|
|
111
|
+
default:
|
|
112
|
+
return hermite(keys, i, t, span);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function segmentIndex(keys, frame) {
|
|
116
|
+
let lo = 0;
|
|
117
|
+
let hi = keys.length - 1;
|
|
118
|
+
while (lo < hi) {
|
|
119
|
+
const mid = lo + hi + 1 >> 1;
|
|
120
|
+
if (keys[mid].frame <= frame) lo = mid;
|
|
121
|
+
else hi = mid - 1;
|
|
122
|
+
}
|
|
123
|
+
return Math.min(lo, keys.length - 2);
|
|
124
|
+
}
|
|
125
|
+
function hermite(keys, i, t, span) {
|
|
126
|
+
const k0 = keys[i];
|
|
127
|
+
const k1 = keys[i + 1];
|
|
128
|
+
const m0 = tangent(keys, i, "out");
|
|
129
|
+
const m1 = tangent(keys, i + 1, "in");
|
|
130
|
+
const t2 = t * t;
|
|
131
|
+
const t3 = t2 * t;
|
|
132
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
133
|
+
const h10 = t3 - 2 * t2 + t;
|
|
134
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
135
|
+
const h11 = t3 - t2;
|
|
136
|
+
return h00 * k0.value + h10 * span * m0 + h01 * k1.value + h11 * span * m1;
|
|
137
|
+
}
|
|
138
|
+
function tangent(keys, i, side) {
|
|
139
|
+
const prev = keys[i - 1];
|
|
140
|
+
const next = keys[i + 1];
|
|
141
|
+
if (!prev || !next) return 0;
|
|
142
|
+
if (side === "out" && prev.interpolation === "constant") return 0;
|
|
143
|
+
if (side === "in" && keys[i].interpolation === "constant") return 0;
|
|
144
|
+
const span = next.frame - prev.frame;
|
|
145
|
+
if (span <= 0) return 0;
|
|
146
|
+
return (next.value - prev.value) / span;
|
|
147
|
+
}
|
|
148
|
+
function serializeChannel(channel) {
|
|
149
|
+
if (isEmptyChannel(channel)) return void 0;
|
|
150
|
+
return {
|
|
151
|
+
keys: channel.keys.map((key) => key.interpolation === DEFAULT_INTERPOLATION ? { frame: key.frame, value: key.value } : { frame: key.frame, value: key.value, interpolation: key.interpolation })
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function deserializeChannel(data) {
|
|
155
|
+
if (!data || typeof data !== "object") return EMPTY_CHANNEL;
|
|
156
|
+
const keys = data.keys;
|
|
157
|
+
if (!Array.isArray(keys)) return EMPTY_CHANNEL;
|
|
158
|
+
return createChannel(keys);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// packages/runtime/src/expressions/time.ts
|
|
162
|
+
var DEFAULT_FPS = 30;
|
|
163
|
+
var MIN_FRAME = 1;
|
|
164
|
+
var TimeState = class {
|
|
165
|
+
constructor() {
|
|
166
|
+
/** Exact, possibly fractional frame. Never below MIN_FRAME. */
|
|
167
|
+
this._frame = MIN_FRAME;
|
|
168
|
+
this._fps = DEFAULT_FPS;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Set the current frame. Fractional values are preserved; only the lower
|
|
172
|
+
* bound is enforced. Non-finite input is ignored.
|
|
173
|
+
*/
|
|
174
|
+
setFrame(frame) {
|
|
175
|
+
if (!Number.isFinite(frame)) return;
|
|
176
|
+
this._frame = Math.max(MIN_FRAME, frame);
|
|
177
|
+
}
|
|
178
|
+
/** Set the current time in seconds, keeping the sub-frame remainder. */
|
|
179
|
+
setTime(seconds) {
|
|
180
|
+
if (!Number.isFinite(seconds)) return;
|
|
181
|
+
this.setFrame(seconds * this._fps + MIN_FRAME);
|
|
182
|
+
}
|
|
183
|
+
setFps(fps) {
|
|
184
|
+
if (!Number.isFinite(fps)) return;
|
|
185
|
+
this._fps = Math.max(1, fps);
|
|
186
|
+
}
|
|
187
|
+
/** Integer frame — $F. */
|
|
188
|
+
get frame() {
|
|
189
|
+
return Math.floor(this._frame);
|
|
190
|
+
}
|
|
191
|
+
/** Fractional frame — $FF. */
|
|
192
|
+
get frameFraction() {
|
|
193
|
+
return this._frame;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Seconds — $T. Derived from the fractional frame, so it round-trips.
|
|
197
|
+
*
|
|
198
|
+
* Zero at the first frame, which is Houdini's convention: $T = ($FF - 1)/$FPS.
|
|
199
|
+
* The previous definition was frame/fps, making $T equal 1/fps at frame 1 —
|
|
200
|
+
* so `sin($T)` started part-way through its cycle and an animation's first
|
|
201
|
+
* frame was never t=0. Changed 2026-09-07 because Marcus named Houdini as the
|
|
202
|
+
* reference for these variables, and because "time starts at zero" is the
|
|
203
|
+
* thing anybody writing an expression will assume.
|
|
204
|
+
*/
|
|
205
|
+
get time() {
|
|
206
|
+
return (this._frame - MIN_FRAME) / this._fps;
|
|
207
|
+
}
|
|
208
|
+
/** Frame rate — $FPS. */
|
|
209
|
+
get fps() {
|
|
210
|
+
return this._fps;
|
|
211
|
+
}
|
|
212
|
+
/** Immutable snapshot of the four time variables. */
|
|
213
|
+
snapshot() {
|
|
214
|
+
return {
|
|
215
|
+
frame: this.frame,
|
|
216
|
+
frameFraction: this.frameFraction,
|
|
217
|
+
time: this.time,
|
|
218
|
+
fps: this.fps
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// packages/runtime/src/expressions/bundlePaths.ts
|
|
224
|
+
var REFERENCE = /\b(?:ch|chs|chv)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
225
|
+
function parseBundleFieldPath(path) {
|
|
226
|
+
const local = path.startsWith("./") ? path.slice(2) : path;
|
|
227
|
+
if (local.includes("/")) return null;
|
|
228
|
+
const dot = local.indexOf(".");
|
|
229
|
+
if (dot <= 0 || dot === local.length - 1) return null;
|
|
230
|
+
return { port: local.slice(0, dot), field: local.slice(dot + 1) };
|
|
231
|
+
}
|
|
232
|
+
function bundleFieldReferences(expression) {
|
|
233
|
+
const references = [];
|
|
234
|
+
REFERENCE.lastIndex = 0;
|
|
235
|
+
let match;
|
|
236
|
+
while ((match = REFERENCE.exec(expression)) !== null) {
|
|
237
|
+
const parsed = parseBundleFieldPath(match[1]);
|
|
238
|
+
if (parsed) references.push({ ...parsed, path: match[1] });
|
|
239
|
+
}
|
|
240
|
+
return references;
|
|
241
|
+
}
|
|
242
|
+
function remoteBundlePath(path) {
|
|
243
|
+
const local = path.startsWith("./") ? path.slice(2) : path;
|
|
244
|
+
const slash = local.lastIndexOf("/");
|
|
245
|
+
if (slash === -1) return null;
|
|
246
|
+
const last = local.slice(slash + 1);
|
|
247
|
+
const dot = last.indexOf(".");
|
|
248
|
+
if (dot <= 0 || dot === last.length - 1) return null;
|
|
249
|
+
const node = local.slice(0, slash).split("/").filter((part) => part && part !== "." && part !== "..").pop();
|
|
250
|
+
if (!node) return null;
|
|
251
|
+
return { node, port: last.slice(0, dot), field: last.slice(dot + 1) };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// packages/runtime/src/expressions/math.ts
|
|
255
|
+
function fit(value, oldMin, oldMax, newMin, newMax) {
|
|
256
|
+
if (oldMax === oldMin) return newMin;
|
|
257
|
+
const t = (value - oldMin) / (oldMax - oldMin);
|
|
258
|
+
return newMin + t * (newMax - newMin);
|
|
259
|
+
}
|
|
260
|
+
function fit01(value, newMin, newMax) {
|
|
261
|
+
return newMin + value * (newMax - newMin);
|
|
262
|
+
}
|
|
263
|
+
function clamp(value, min, max) {
|
|
264
|
+
return Math.max(min, Math.min(max, value));
|
|
265
|
+
}
|
|
266
|
+
function lerp(a, b, t) {
|
|
267
|
+
return a + (b - a) * t;
|
|
268
|
+
}
|
|
269
|
+
function smooth(value, min, max) {
|
|
270
|
+
if (value <= min) return 0;
|
|
271
|
+
if (value >= max) return 1;
|
|
272
|
+
const t = (value - min) / (max - min);
|
|
273
|
+
return t * t * (3 - 2 * t);
|
|
274
|
+
}
|
|
275
|
+
function noise(...coords) {
|
|
276
|
+
if (coords.length === 0) return 0;
|
|
277
|
+
let result = 0;
|
|
278
|
+
const frequencies = [1, 2.3, 4.7, 8.1];
|
|
279
|
+
const amplitudes = [1, 0.5, 0.25, 0.125];
|
|
280
|
+
for (let i = 0; i < frequencies.length; i++) {
|
|
281
|
+
let sum = 0;
|
|
282
|
+
for (let j = 0; j < coords.length; j++) {
|
|
283
|
+
sum += Math.sin(coords[j] * frequencies[i] * (j + 1) * 1.7 + j * 3.14159);
|
|
284
|
+
}
|
|
285
|
+
result += sum * amplitudes[i];
|
|
286
|
+
}
|
|
287
|
+
return Math.sin(result * 0.5);
|
|
288
|
+
}
|
|
289
|
+
function random(seed) {
|
|
290
|
+
const x = Math.sin(seed * 12.9898) * 43758.5453;
|
|
291
|
+
return x - Math.floor(x);
|
|
292
|
+
}
|
|
293
|
+
function padzero(digits, value) {
|
|
294
|
+
return String(Math.floor(value)).padStart(digits, "0");
|
|
295
|
+
}
|
|
296
|
+
function radians(degrees2) {
|
|
297
|
+
return degrees2 * Math.PI / 180;
|
|
298
|
+
}
|
|
299
|
+
function degrees(radians2) {
|
|
300
|
+
return radians2 * 180 / Math.PI;
|
|
301
|
+
}
|
|
302
|
+
function sind(degrees2) {
|
|
303
|
+
return Math.sin(radians(degrees2));
|
|
304
|
+
}
|
|
305
|
+
function cosd(degrees2) {
|
|
306
|
+
return Math.cos(radians(degrees2));
|
|
307
|
+
}
|
|
308
|
+
function tand(degrees2) {
|
|
309
|
+
return Math.tan(radians(degrees2));
|
|
310
|
+
}
|
|
311
|
+
var MATH_SCOPE = {
|
|
312
|
+
// Trigonometry, in radians
|
|
313
|
+
sin: Math.sin,
|
|
314
|
+
cos: Math.cos,
|
|
315
|
+
tan: Math.tan,
|
|
316
|
+
asin: Math.asin,
|
|
317
|
+
acos: Math.acos,
|
|
318
|
+
atan: Math.atan,
|
|
319
|
+
atan2: Math.atan2,
|
|
320
|
+
sinh: Math.sinh,
|
|
321
|
+
cosh: Math.cosh,
|
|
322
|
+
tanh: Math.tanh,
|
|
323
|
+
// Trigonometry, in degrees, for formulae carried over from Houdini
|
|
324
|
+
sind,
|
|
325
|
+
cosd,
|
|
326
|
+
tand,
|
|
327
|
+
radians,
|
|
328
|
+
degrees,
|
|
329
|
+
// Powers and logs
|
|
330
|
+
sqrt: Math.sqrt,
|
|
331
|
+
cbrt: Math.cbrt,
|
|
332
|
+
pow: Math.pow,
|
|
333
|
+
exp: Math.exp,
|
|
334
|
+
log: Math.log,
|
|
335
|
+
log2: Math.log2,
|
|
336
|
+
log10: Math.log10,
|
|
337
|
+
hypot: Math.hypot,
|
|
338
|
+
// Rounding and sign
|
|
339
|
+
abs: Math.abs,
|
|
340
|
+
sign: Math.sign,
|
|
341
|
+
floor: Math.floor,
|
|
342
|
+
ceil: Math.ceil,
|
|
343
|
+
round: Math.round,
|
|
344
|
+
trunc: Math.trunc,
|
|
345
|
+
min: Math.min,
|
|
346
|
+
max: Math.max,
|
|
347
|
+
// Constants
|
|
348
|
+
PI: Math.PI,
|
|
349
|
+
TAU: Math.PI * 2,
|
|
350
|
+
E: Math.E
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// packages/runtime/src/expressions/preprocess.ts
|
|
354
|
+
var DOLLAR_TIME_PATTERN = /\$(FPS|FF|F|T)\b/g;
|
|
355
|
+
var BARE_TIME_PATTERN = /\b(?:time|frame|fframe|fps)\b/;
|
|
356
|
+
var DOLLAR_TO_IDENTIFIER = {
|
|
357
|
+
FPS: "fps",
|
|
358
|
+
FF: "fframe",
|
|
359
|
+
F: "frame",
|
|
360
|
+
T: "time"
|
|
361
|
+
};
|
|
362
|
+
function codeSpans(source) {
|
|
363
|
+
const spans = [];
|
|
364
|
+
let spanStart = 0;
|
|
365
|
+
let index = 0;
|
|
366
|
+
while (index < source.length) {
|
|
367
|
+
const char = source[index];
|
|
368
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
369
|
+
spans.push({ start: spanStart, end: index });
|
|
370
|
+
const quote = char;
|
|
371
|
+
index += 1;
|
|
372
|
+
while (index < source.length) {
|
|
373
|
+
if (source[index] === "\\") {
|
|
374
|
+
index += 2;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (source[index] === quote) {
|
|
378
|
+
index += 1;
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
index += 1;
|
|
382
|
+
}
|
|
383
|
+
spanStart = index;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
index += 1;
|
|
387
|
+
}
|
|
388
|
+
spans.push({ start: spanStart, end: source.length });
|
|
389
|
+
return spans;
|
|
390
|
+
}
|
|
391
|
+
function preprocessExpression(source) {
|
|
392
|
+
if (!source.includes("$")) return source;
|
|
393
|
+
let out = "";
|
|
394
|
+
let cursor = 0;
|
|
395
|
+
for (const span of codeSpans(source)) {
|
|
396
|
+
if (span.start > cursor) out += source.slice(cursor, span.start);
|
|
397
|
+
out += source.slice(span.start, span.end).replace(DOLLAR_TIME_PATTERN, (_match, key) => DOLLAR_TO_IDENTIFIER[key]);
|
|
398
|
+
cursor = span.end;
|
|
399
|
+
}
|
|
400
|
+
if (cursor < source.length) out += source.slice(cursor);
|
|
401
|
+
return out;
|
|
402
|
+
}
|
|
403
|
+
function hasTimeReference(source) {
|
|
404
|
+
if (BARE_TIME_PATTERN.test(source)) return true;
|
|
405
|
+
DOLLAR_TIME_PATTERN.lastIndex = 0;
|
|
406
|
+
return DOLLAR_TIME_PATTERN.test(source);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// packages/runtime/src/expressions/evaluator.ts
|
|
410
|
+
var DEPENDENCY_PATTERN = /\b(?:ch|chs|chv)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
411
|
+
var ExpressionEvaluator = class {
|
|
412
|
+
constructor(scope = null) {
|
|
413
|
+
this.timeState = new TimeState();
|
|
414
|
+
this.compiledCache = /* @__PURE__ */ new Map();
|
|
415
|
+
this.scope = scope;
|
|
416
|
+
}
|
|
417
|
+
/** Bind (or rebind) the host scope. Clears nothing: compilation is scope-free. */
|
|
418
|
+
setScope(scope) {
|
|
419
|
+
this.scope = scope;
|
|
420
|
+
}
|
|
421
|
+
getScope() {
|
|
422
|
+
return this.scope;
|
|
423
|
+
}
|
|
424
|
+
// ============ Time ============
|
|
425
|
+
/** Set the current frame. Fractional frames are preserved. */
|
|
426
|
+
setFrame(frame) {
|
|
427
|
+
this.timeState.setFrame(frame);
|
|
428
|
+
}
|
|
429
|
+
/** Set the current time in seconds, keeping the sub-frame remainder. */
|
|
430
|
+
setTime(seconds) {
|
|
431
|
+
this.timeState.setTime(seconds);
|
|
432
|
+
}
|
|
433
|
+
setFps(fps) {
|
|
434
|
+
this.timeState.setFps(fps);
|
|
435
|
+
}
|
|
436
|
+
/** seconds — $T */
|
|
437
|
+
get time() {
|
|
438
|
+
return this.timeState.time;
|
|
439
|
+
}
|
|
440
|
+
/** integer frame — $F */
|
|
441
|
+
get frame() {
|
|
442
|
+
return this.timeState.frame;
|
|
443
|
+
}
|
|
444
|
+
/** fractional frame — $FF */
|
|
445
|
+
get fframe() {
|
|
446
|
+
return this.timeState.frameFraction;
|
|
447
|
+
}
|
|
448
|
+
/** rate — $FPS */
|
|
449
|
+
get fps() {
|
|
450
|
+
return this.timeState.fps;
|
|
451
|
+
}
|
|
452
|
+
// ============ Compile / evaluate ============
|
|
453
|
+
compile(expression) {
|
|
454
|
+
const cached = this.compiledCache.get(expression);
|
|
455
|
+
if (cached) return cached;
|
|
456
|
+
const dependencies = this.extractDependencies(expression);
|
|
457
|
+
const isTimeDependent = hasTimeReference(expression);
|
|
458
|
+
const compiledSource = preprocessExpression(expression);
|
|
459
|
+
try {
|
|
460
|
+
const fnBody = `
|
|
461
|
+
with (context) {
|
|
462
|
+
return (${compiledSource});
|
|
463
|
+
}
|
|
464
|
+
`;
|
|
465
|
+
const evaluator = new Function("context", fnBody);
|
|
466
|
+
const compiled = {
|
|
467
|
+
source: expression,
|
|
468
|
+
compiledSource,
|
|
469
|
+
evaluate: evaluator,
|
|
470
|
+
dependencies,
|
|
471
|
+
isTimeDependent
|
|
472
|
+
};
|
|
473
|
+
this.compiledCache.set(expression, compiled);
|
|
474
|
+
return compiled;
|
|
475
|
+
} catch (err) {
|
|
476
|
+
return {
|
|
477
|
+
source: expression,
|
|
478
|
+
compiledSource,
|
|
479
|
+
evaluate: () => void 0,
|
|
480
|
+
dependencies,
|
|
481
|
+
isTimeDependent,
|
|
482
|
+
error: err instanceof Error ? err.message : String(err)
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
evaluate(expression, node) {
|
|
487
|
+
const compiled = this.compile(expression);
|
|
488
|
+
if (compiled.error) return { value: void 0, error: compiled.error };
|
|
489
|
+
try {
|
|
490
|
+
return { value: compiled.evaluate(this.createContext(node)) };
|
|
491
|
+
} catch (err) {
|
|
492
|
+
return {
|
|
493
|
+
value: void 0,
|
|
494
|
+
error: err instanceof Error ? err.message : String(err)
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/** Build the context an expression sees. */
|
|
499
|
+
createContext(node) {
|
|
500
|
+
return {
|
|
501
|
+
time: this.timeState.time,
|
|
502
|
+
frame: this.timeState.frame,
|
|
503
|
+
fframe: this.timeState.frameFraction,
|
|
504
|
+
fps: this.timeState.fps,
|
|
505
|
+
self: node,
|
|
506
|
+
ch: (path) => this.ch(node, path),
|
|
507
|
+
chs: (path) => this.chs(node, path),
|
|
508
|
+
chv: (path) => this.chv(node, path),
|
|
509
|
+
opexist: (path) => this.scope?.nodeExists(node, path) ? 1 : 0,
|
|
510
|
+
opinput: (index) => this.scope?.inputNode(node, index) ?? null,
|
|
511
|
+
fit,
|
|
512
|
+
fit01,
|
|
513
|
+
clamp,
|
|
514
|
+
lerp,
|
|
515
|
+
smooth,
|
|
516
|
+
noise,
|
|
517
|
+
random,
|
|
518
|
+
padzero,
|
|
519
|
+
// Bare maths — `sin($FF * 0.5)` rather than `Math.sin(...)`. Spread
|
|
520
|
+
// before `Math` so nothing here can shadow the namespace itself.
|
|
521
|
+
...MATH_SCOPE,
|
|
522
|
+
Math
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
// ============ Channel functions ============
|
|
526
|
+
ch(node, path) {
|
|
527
|
+
const resolved = this.resolveParam(node, path);
|
|
528
|
+
if (!resolved) return 0;
|
|
529
|
+
const value = resolved.value;
|
|
530
|
+
if (typeof value === "number") return value;
|
|
531
|
+
if (typeof value === "string") return parseFloat(value) || 0;
|
|
532
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
533
|
+
return 0;
|
|
534
|
+
}
|
|
535
|
+
chs(node, path) {
|
|
536
|
+
const resolved = this.resolveParam(node, path);
|
|
537
|
+
if (!resolved) return "";
|
|
538
|
+
return String(resolved.value ?? "");
|
|
539
|
+
}
|
|
540
|
+
chv(node, path) {
|
|
541
|
+
const resolved = this.resolveParam(node, path);
|
|
542
|
+
if (!resolved) return [];
|
|
543
|
+
const value = resolved.value;
|
|
544
|
+
if (Array.isArray(value)) {
|
|
545
|
+
return value.map((v) => typeof v === "number" ? v : parseFloat(String(v)) || 0);
|
|
546
|
+
}
|
|
547
|
+
if (typeof value === "number") return [value];
|
|
548
|
+
return [];
|
|
549
|
+
}
|
|
550
|
+
resolveParam(node, path) {
|
|
551
|
+
return this.scope?.resolveParam(node, path) ?? null;
|
|
552
|
+
}
|
|
553
|
+
/** True if a referenced path resolves from this node. */
|
|
554
|
+
validatePath(fromNode, path) {
|
|
555
|
+
return this.resolveParam(fromNode, path) !== null;
|
|
556
|
+
}
|
|
557
|
+
/** Parameter paths referenced through ch/chs/chv. */
|
|
558
|
+
extractDependencies(expression) {
|
|
559
|
+
const deps = [];
|
|
560
|
+
DEPENDENCY_PATTERN.lastIndex = 0;
|
|
561
|
+
let match;
|
|
562
|
+
while ((match = DEPENDENCY_PATTERN.exec(expression)) !== null) {
|
|
563
|
+
deps.push(match[1]);
|
|
564
|
+
}
|
|
565
|
+
return deps;
|
|
566
|
+
}
|
|
567
|
+
/** True if the expression references time, in either the bare or `$` spelling. */
|
|
568
|
+
hasTimeReference(expression) {
|
|
569
|
+
return hasTimeReference(expression);
|
|
570
|
+
}
|
|
571
|
+
clearCache() {
|
|
572
|
+
this.compiledCache.clear();
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
export {
|
|
577
|
+
isEmptyChannel,
|
|
578
|
+
setKey,
|
|
579
|
+
deleteKey,
|
|
580
|
+
sampleChannel,
|
|
581
|
+
serializeChannel,
|
|
582
|
+
deserializeChannel,
|
|
583
|
+
frameRange,
|
|
584
|
+
runFrameRange,
|
|
585
|
+
DEFAULT_FPS,
|
|
586
|
+
MIN_FRAME,
|
|
587
|
+
TimeState,
|
|
588
|
+
ExpressionEvaluator,
|
|
589
|
+
parseBundleFieldPath,
|
|
590
|
+
bundleFieldReferences,
|
|
591
|
+
remoteBundlePath
|
|
592
|
+
};
|
|
593
|
+
//# sourceMappingURL=chunk-CO22XHNG.js.map
|