@vizij/node-graph-authoring 0.0.2
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/LICENSE +201 -0
- package/README.md +24 -0
- package/dist/index.cjs +2367 -0
- package/dist/index.d.cts +146 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +2312 -0
- package/package.json +53 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2312 @@
|
|
|
1
|
+
// src/graphBuilder.ts
|
|
2
|
+
import { buildAnimatableValue } from "@vizij/utils";
|
|
3
|
+
import { SELF_BINDING_ID as SELF_BINDING_ID2 } from "@vizij/utils";
|
|
4
|
+
|
|
5
|
+
// src/state.ts
|
|
6
|
+
import {
|
|
7
|
+
cloneRemapSettings,
|
|
8
|
+
SELF_BINDING_ID
|
|
9
|
+
} from "@vizij/utils";
|
|
10
|
+
|
|
11
|
+
// src/operators.ts
|
|
12
|
+
import { requireNodeSignature } from "@vizij/node-graph-wasm/metadata";
|
|
13
|
+
var OPERATOR_TYPES = [
|
|
14
|
+
"spring",
|
|
15
|
+
"damp",
|
|
16
|
+
"slew"
|
|
17
|
+
];
|
|
18
|
+
function valueJsonToNumber(value) {
|
|
19
|
+
if (!value || typeof value !== "object") {
|
|
20
|
+
return typeof value === "number" ? value : 0;
|
|
21
|
+
}
|
|
22
|
+
if ("float" in value && typeof value.float === "number") {
|
|
23
|
+
return value.float;
|
|
24
|
+
}
|
|
25
|
+
if ("int" in value && typeof value.int === "number") {
|
|
26
|
+
return value.int;
|
|
27
|
+
}
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
var operatorDefinitionMap = /* @__PURE__ */ new Map();
|
|
31
|
+
OPERATOR_TYPES.forEach((type) => {
|
|
32
|
+
const signature = requireNodeSignature(type);
|
|
33
|
+
const params = signature.params.map(
|
|
34
|
+
(param) => ({
|
|
35
|
+
id: param.id,
|
|
36
|
+
label: param.label,
|
|
37
|
+
doc: param.doc,
|
|
38
|
+
min: param.min ?? void 0,
|
|
39
|
+
max: param.max ?? void 0,
|
|
40
|
+
defaultValue: valueJsonToNumber(param.default_json)
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
operatorDefinitionMap.set(type, {
|
|
44
|
+
type,
|
|
45
|
+
nodeType: signature.type_id,
|
|
46
|
+
label: signature.name,
|
|
47
|
+
description: signature.doc,
|
|
48
|
+
inputs: signature.inputs.map((input) => input.id),
|
|
49
|
+
params
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
function getBindingOperatorDefinition(type) {
|
|
53
|
+
const definition = operatorDefinitionMap.get(type);
|
|
54
|
+
if (!definition) {
|
|
55
|
+
throw new Error(`Unknown binding operator type '${type}'.`);
|
|
56
|
+
}
|
|
57
|
+
return definition;
|
|
58
|
+
}
|
|
59
|
+
var bindingOperatorDefinitions = OPERATOR_TYPES.map((type) => getBindingOperatorDefinition(type));
|
|
60
|
+
function createDefaultOperatorSettings(type) {
|
|
61
|
+
const definition = getBindingOperatorDefinition(type);
|
|
62
|
+
const params = {};
|
|
63
|
+
definition.params.forEach((param) => {
|
|
64
|
+
params[param.id] = param.defaultValue;
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
type,
|
|
68
|
+
enabled: false,
|
|
69
|
+
params
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function ensureOperatorParams(operator) {
|
|
73
|
+
const definition = getBindingOperatorDefinition(operator.type);
|
|
74
|
+
const params = {};
|
|
75
|
+
definition.params.forEach((param) => {
|
|
76
|
+
const value = operator.params?.[param.id];
|
|
77
|
+
params[param.id] = typeof value === "number" ? value : param.defaultValue;
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
type: operator.type,
|
|
81
|
+
enabled: !!operator.enabled,
|
|
82
|
+
params
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
var bindingOperatorTypes = OPERATOR_TYPES;
|
|
86
|
+
|
|
87
|
+
// src/state.ts
|
|
88
|
+
var VECTOR_ANIMATABLE_TYPES = /* @__PURE__ */ new Set(["vector2", "vector3", "euler", "rgb"]);
|
|
89
|
+
function deriveComponentValueType(component) {
|
|
90
|
+
if (component.component) {
|
|
91
|
+
return "scalar";
|
|
92
|
+
}
|
|
93
|
+
return VECTOR_ANIMATABLE_TYPES.has(component.animatableType) ? "vector" : "scalar";
|
|
94
|
+
}
|
|
95
|
+
function isBindingValueType(value) {
|
|
96
|
+
return value === "scalar" || value === "vector";
|
|
97
|
+
}
|
|
98
|
+
function getTargetValueType(target) {
|
|
99
|
+
return isBindingValueType(target.valueType) ? target.valueType : "scalar";
|
|
100
|
+
}
|
|
101
|
+
function sanitizeSlotValueType(value, targetType) {
|
|
102
|
+
return isBindingValueType(value) ? value : targetType;
|
|
103
|
+
}
|
|
104
|
+
function bindingTargetFromComponent(component) {
|
|
105
|
+
return {
|
|
106
|
+
id: component.id,
|
|
107
|
+
defaultValue: component.defaultValue,
|
|
108
|
+
range: {
|
|
109
|
+
min: component.range.min,
|
|
110
|
+
max: component.range.max
|
|
111
|
+
},
|
|
112
|
+
valueType: deriveComponentValueType(component)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function bindingTargetFromInput(input) {
|
|
116
|
+
return {
|
|
117
|
+
id: input.id,
|
|
118
|
+
defaultValue: input.defaultValue,
|
|
119
|
+
range: {
|
|
120
|
+
min: input.range.min,
|
|
121
|
+
max: input.range.max
|
|
122
|
+
},
|
|
123
|
+
valueType: "scalar"
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
var DEFAULT_INPUT_RANGE = { min: -1, max: 1 };
|
|
127
|
+
var DEFAULT_INPUT_ANCHOR = 0;
|
|
128
|
+
var EPSILON = 1e-6;
|
|
129
|
+
var LEGACY_SLOT_PATTERN = /^slot_(\d+)$/i;
|
|
130
|
+
var ALIAS_SANITIZE_PATTERN = /[^A-Za-z0-9_]+/g;
|
|
131
|
+
var PRIMARY_SLOT_ID = "s1";
|
|
132
|
+
var PRIMARY_SLOT_ALIAS = "s1";
|
|
133
|
+
function createDefaultOperators() {
|
|
134
|
+
return bindingOperatorTypes.map(
|
|
135
|
+
(type) => createDefaultOperatorSettings(type)
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
function normalizeOperator(operator) {
|
|
139
|
+
const normalized = ensureOperatorParams(operator);
|
|
140
|
+
const definition = getBindingOperatorDefinition(normalized.type);
|
|
141
|
+
const params = {};
|
|
142
|
+
definition.params.forEach((param) => {
|
|
143
|
+
const value = normalized.params[param.id];
|
|
144
|
+
params[param.id] = typeof value === "number" ? value : param.defaultValue;
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
type: normalized.type,
|
|
148
|
+
enabled: !!normalized.enabled,
|
|
149
|
+
params
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function ensureOperators(binding) {
|
|
153
|
+
const existing = /* @__PURE__ */ new Map();
|
|
154
|
+
(binding.operators ?? []).forEach((operator) => {
|
|
155
|
+
try {
|
|
156
|
+
existing.set(operator.type, normalizeOperator(operator));
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
return bindingOperatorTypes.map((type) => {
|
|
161
|
+
const current = existing.get(type);
|
|
162
|
+
if (current) {
|
|
163
|
+
return current;
|
|
164
|
+
}
|
|
165
|
+
return createDefaultOperatorSettings(type);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function paramsEqual(a, b) {
|
|
169
|
+
const aKeys = Object.keys(a);
|
|
170
|
+
const bKeys = Object.keys(b);
|
|
171
|
+
if (aKeys.length !== bKeys.length) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
for (const key of aKeys) {
|
|
175
|
+
if (a[key] !== b[key]) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
function operatorsEqual(a, b) {
|
|
182
|
+
if (!a || a.length !== b.length) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
186
|
+
const left = a[index];
|
|
187
|
+
const right = b[index];
|
|
188
|
+
if (left.type !== right.type || left.enabled !== right.enabled) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
if (!paramsEqual(left.params, right.params)) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
function normalizeBindingWithOperators(binding) {
|
|
198
|
+
const operators = ensureOperators(binding);
|
|
199
|
+
if (operatorsEqual(binding.operators, operators)) {
|
|
200
|
+
return { binding, operators };
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
binding: {
|
|
204
|
+
...binding,
|
|
205
|
+
operators
|
|
206
|
+
},
|
|
207
|
+
operators
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function defaultSlotId(index) {
|
|
211
|
+
return `s${index + 1}`;
|
|
212
|
+
}
|
|
213
|
+
function normalizeSlotId(value, index) {
|
|
214
|
+
if (value && value.length > 0) {
|
|
215
|
+
const match = value.match(LEGACY_SLOT_PATTERN);
|
|
216
|
+
if (match) {
|
|
217
|
+
const suffix = match[1] ?? String(index + 1);
|
|
218
|
+
return `s${suffix}`;
|
|
219
|
+
}
|
|
220
|
+
return value;
|
|
221
|
+
}
|
|
222
|
+
return defaultSlotId(index);
|
|
223
|
+
}
|
|
224
|
+
function normalizeSlotAlias(value, fallback, index) {
|
|
225
|
+
if (value && value.length > 0) {
|
|
226
|
+
const match = value.match(LEGACY_SLOT_PATTERN);
|
|
227
|
+
if (match) {
|
|
228
|
+
const suffix = match[1] ?? String(index + 1);
|
|
229
|
+
return { alias: `s${suffix}`, replaced: value };
|
|
230
|
+
}
|
|
231
|
+
return { alias: value, replaced: null };
|
|
232
|
+
}
|
|
233
|
+
if (fallback && fallback.length > 0) {
|
|
234
|
+
return { alias: fallback, replaced: null };
|
|
235
|
+
}
|
|
236
|
+
return { alias: defaultSlotId(index), replaced: null };
|
|
237
|
+
}
|
|
238
|
+
function sanitizeAliasInput(raw, fallback, index) {
|
|
239
|
+
const trimmed = raw.trim();
|
|
240
|
+
if (trimmed.length === 0) {
|
|
241
|
+
return fallback || defaultSlotId(index);
|
|
242
|
+
}
|
|
243
|
+
let sanitized = trimmed.replace(/\s+/g, "_").replace(ALIAS_SANITIZE_PATTERN, "");
|
|
244
|
+
if (sanitized.length === 0) {
|
|
245
|
+
sanitized = fallback || defaultSlotId(index);
|
|
246
|
+
}
|
|
247
|
+
if (/^\d/.test(sanitized)) {
|
|
248
|
+
sanitized = `s${sanitized}`;
|
|
249
|
+
}
|
|
250
|
+
return sanitized;
|
|
251
|
+
}
|
|
252
|
+
function ensureUniqueAlias(candidate, existing) {
|
|
253
|
+
if (!existing.has(candidate.toLowerCase())) {
|
|
254
|
+
existing.add(candidate.toLowerCase());
|
|
255
|
+
return candidate;
|
|
256
|
+
}
|
|
257
|
+
let suffix = 2;
|
|
258
|
+
let next = `${candidate}_${suffix}`;
|
|
259
|
+
while (existing.has(next.toLowerCase())) {
|
|
260
|
+
suffix += 1;
|
|
261
|
+
next = `${candidate}_${suffix}`;
|
|
262
|
+
}
|
|
263
|
+
existing.add(next.toLowerCase());
|
|
264
|
+
return next;
|
|
265
|
+
}
|
|
266
|
+
function escapeRegExp(value) {
|
|
267
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
268
|
+
}
|
|
269
|
+
function rewriteLegacyExpression(expression, replacements) {
|
|
270
|
+
if (expression.trim().length === 0) {
|
|
271
|
+
return expression;
|
|
272
|
+
}
|
|
273
|
+
return expression.replace(/\bslot_(\d+)\b/gi, (match, digits) => {
|
|
274
|
+
const replacement = replacements.get(match);
|
|
275
|
+
if (replacement) {
|
|
276
|
+
return replacement;
|
|
277
|
+
}
|
|
278
|
+
return `s${digits}`;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function clamp(value, min, max) {
|
|
282
|
+
return Math.min(max, Math.max(min, value));
|
|
283
|
+
}
|
|
284
|
+
function isFiniteNumber(value) {
|
|
285
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
286
|
+
}
|
|
287
|
+
function deriveOutputDefaults(target) {
|
|
288
|
+
const { min, max } = target.range;
|
|
289
|
+
const anchor = clamp(target.defaultValue, min, max);
|
|
290
|
+
return {
|
|
291
|
+
outLow: min,
|
|
292
|
+
outAnchor: anchor,
|
|
293
|
+
outHigh: max
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function deriveInputDefaults() {
|
|
297
|
+
return {
|
|
298
|
+
inLow: DEFAULT_INPUT_RANGE.min,
|
|
299
|
+
inAnchor: DEFAULT_INPUT_ANCHOR,
|
|
300
|
+
inHigh: DEFAULT_INPUT_RANGE.max
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function migrateLegacyRemap(legacy, target) {
|
|
304
|
+
const inputDefaults = deriveInputDefaults();
|
|
305
|
+
const outputDefaults = deriveOutputDefaults(target);
|
|
306
|
+
const defaults = {
|
|
307
|
+
inLow: inputDefaults.inLow,
|
|
308
|
+
inAnchor: inputDefaults.inAnchor,
|
|
309
|
+
inHigh: inputDefaults.inHigh,
|
|
310
|
+
outLow: outputDefaults.outLow,
|
|
311
|
+
outAnchor: outputDefaults.outAnchor,
|
|
312
|
+
outHigh: outputDefaults.outHigh
|
|
313
|
+
};
|
|
314
|
+
if ("inLow" in legacy && "inHigh" in legacy && "outLow" in legacy && "outHigh" in legacy) {
|
|
315
|
+
const inLow2 = isFiniteNumber(legacy.inLow) ? legacy.inLow : defaults.inLow;
|
|
316
|
+
const inAnchor2 = isFiniteNumber(legacy.inAnchor) ? legacy.inAnchor : defaults.inAnchor;
|
|
317
|
+
const inHigh2 = isFiniteNumber(legacy.inHigh) ? legacy.inHigh : defaults.inHigh;
|
|
318
|
+
let outLow = isFiniteNumber(legacy.outLow) ? legacy.outLow : defaults.outLow;
|
|
319
|
+
let outHigh = isFiniteNumber(legacy.outHigh) ? legacy.outHigh : defaults.outHigh;
|
|
320
|
+
if (outLow > outHigh) {
|
|
321
|
+
const low = outHigh;
|
|
322
|
+
const high = outLow;
|
|
323
|
+
outLow = low;
|
|
324
|
+
outHigh = high;
|
|
325
|
+
}
|
|
326
|
+
const outAnchor2 = clamp(
|
|
327
|
+
isFiniteNumber(legacy.outAnchor) ? legacy.outAnchor : defaults.outAnchor,
|
|
328
|
+
outLow,
|
|
329
|
+
outHigh
|
|
330
|
+
);
|
|
331
|
+
return {
|
|
332
|
+
inLow: inLow2,
|
|
333
|
+
inAnchor: inAnchor2,
|
|
334
|
+
inHigh: inHigh2,
|
|
335
|
+
outLow,
|
|
336
|
+
outAnchor: outAnchor2,
|
|
337
|
+
outHigh
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
const legacyTyped = legacy;
|
|
341
|
+
const inLow = isFiniteNumber(legacyTyped.inMin) ? legacyTyped.inMin : defaults.inLow;
|
|
342
|
+
const inHigh = isFiniteNumber(legacyTyped.inMax) ? legacyTyped.inMax : defaults.inHigh;
|
|
343
|
+
const inAnchor = (inLow + inHigh) / 2;
|
|
344
|
+
const legacyOutMid = isFiniteNumber(legacyTyped.outMin) && isFiniteNumber(legacyTyped.outMax) ? (legacyTyped.outMin + legacyTyped.outMax) / 2 : defaults.outAnchor;
|
|
345
|
+
const outAnchor = clamp(legacyOutMid, defaults.outLow, defaults.outHigh);
|
|
346
|
+
return {
|
|
347
|
+
inLow,
|
|
348
|
+
inAnchor,
|
|
349
|
+
inHigh,
|
|
350
|
+
outLow: defaults.outLow,
|
|
351
|
+
outAnchor,
|
|
352
|
+
outHigh: defaults.outHigh
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function normalizeRemap(remap, target) {
|
|
356
|
+
if (!remap) {
|
|
357
|
+
return createDefaultRemap(target);
|
|
358
|
+
}
|
|
359
|
+
return migrateLegacyRemap(remap, target);
|
|
360
|
+
}
|
|
361
|
+
function cloneRemap(remap) {
|
|
362
|
+
return cloneRemapSettings(remap);
|
|
363
|
+
}
|
|
364
|
+
function sanitizeRemap(remap, target) {
|
|
365
|
+
const normalized = normalizeRemap(remap, target);
|
|
366
|
+
const outputDefaults = deriveOutputDefaults(target);
|
|
367
|
+
if (!Number.isFinite(normalized.outLow)) {
|
|
368
|
+
normalized.outLow = outputDefaults.outLow;
|
|
369
|
+
}
|
|
370
|
+
if (!Number.isFinite(normalized.outHigh)) {
|
|
371
|
+
normalized.outHigh = outputDefaults.outHigh;
|
|
372
|
+
}
|
|
373
|
+
if (!Number.isFinite(normalized.outAnchor)) {
|
|
374
|
+
normalized.outAnchor = outputDefaults.outAnchor;
|
|
375
|
+
}
|
|
376
|
+
if (normalized.outLow > normalized.outHigh) {
|
|
377
|
+
const low = normalized.outHigh;
|
|
378
|
+
const high = normalized.outLow;
|
|
379
|
+
normalized.outLow = low;
|
|
380
|
+
normalized.outHigh = high;
|
|
381
|
+
}
|
|
382
|
+
normalized.outAnchor = clamp(
|
|
383
|
+
normalized.outAnchor,
|
|
384
|
+
normalized.outLow,
|
|
385
|
+
normalized.outHigh
|
|
386
|
+
);
|
|
387
|
+
return normalized;
|
|
388
|
+
}
|
|
389
|
+
function createDefaultRemap(target) {
|
|
390
|
+
const inputDefaults = deriveInputDefaults();
|
|
391
|
+
const outputDefaults = deriveOutputDefaults(target);
|
|
392
|
+
return {
|
|
393
|
+
inLow: inputDefaults.inLow,
|
|
394
|
+
inAnchor: inputDefaults.inAnchor,
|
|
395
|
+
inHigh: inputDefaults.inHigh,
|
|
396
|
+
outLow: outputDefaults.outLow,
|
|
397
|
+
outAnchor: outputDefaults.outAnchor,
|
|
398
|
+
outHigh: outputDefaults.outHigh
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function createDefaultBindings(components) {
|
|
402
|
+
const bindings = {};
|
|
403
|
+
components.forEach((component) => {
|
|
404
|
+
bindings[component.id] = createDefaultBinding(component);
|
|
405
|
+
});
|
|
406
|
+
return bindings;
|
|
407
|
+
}
|
|
408
|
+
function createDefaultBinding(component) {
|
|
409
|
+
const remap = createDefaultRemap(component);
|
|
410
|
+
const valueType = getTargetValueType(component);
|
|
411
|
+
return {
|
|
412
|
+
targetId: component.id,
|
|
413
|
+
inputId: null,
|
|
414
|
+
remap,
|
|
415
|
+
slots: [
|
|
416
|
+
{
|
|
417
|
+
id: PRIMARY_SLOT_ID,
|
|
418
|
+
alias: PRIMARY_SLOT_ALIAS,
|
|
419
|
+
inputId: null,
|
|
420
|
+
remap: cloneRemap(remap),
|
|
421
|
+
valueType
|
|
422
|
+
}
|
|
423
|
+
],
|
|
424
|
+
expression: PRIMARY_SLOT_ALIAS,
|
|
425
|
+
operators: createDefaultOperators()
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function createDefaultParentBinding(component) {
|
|
429
|
+
const base = createDefaultBinding(component);
|
|
430
|
+
const ensured = ensurePrimarySlot(base, component);
|
|
431
|
+
const slots = ensured.slots.map((slot, index) => {
|
|
432
|
+
if (index === 0) {
|
|
433
|
+
return {
|
|
434
|
+
...slot,
|
|
435
|
+
alias: "self",
|
|
436
|
+
inputId: SELF_BINDING_ID
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
return slot;
|
|
440
|
+
});
|
|
441
|
+
return {
|
|
442
|
+
...ensured,
|
|
443
|
+
inputId: SELF_BINDING_ID,
|
|
444
|
+
slots,
|
|
445
|
+
expression: "self"
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function ensurePrimarySlot(binding, target) {
|
|
449
|
+
const normalizedBindingRemap = sanitizeRemap(binding.remap, target);
|
|
450
|
+
const targetValueType = getTargetValueType(target);
|
|
451
|
+
const aliasReplacements = /* @__PURE__ */ new Map();
|
|
452
|
+
const sourceSlots = Array.isArray(binding.slots) && binding.slots.length > 0 ? binding.slots : [
|
|
453
|
+
{
|
|
454
|
+
id: PRIMARY_SLOT_ID,
|
|
455
|
+
alias: PRIMARY_SLOT_ALIAS,
|
|
456
|
+
inputId: binding.inputId ?? null,
|
|
457
|
+
remap: cloneRemap(normalizedBindingRemap),
|
|
458
|
+
valueType: targetValueType
|
|
459
|
+
}
|
|
460
|
+
];
|
|
461
|
+
const normalizedSlots = sourceSlots.map(
|
|
462
|
+
(slot, index) => {
|
|
463
|
+
const normalizedId = normalizeSlotId(slot.id, index);
|
|
464
|
+
const { alias: normalizedAlias, replaced } = normalizeSlotAlias(
|
|
465
|
+
slot.alias,
|
|
466
|
+
normalizedId,
|
|
467
|
+
index
|
|
468
|
+
);
|
|
469
|
+
if (replaced && replaced !== normalizedAlias) {
|
|
470
|
+
aliasReplacements.set(replaced, normalizedAlias);
|
|
471
|
+
}
|
|
472
|
+
const slotRemapSource = slot.remap ?? (index === 0 ? normalizedBindingRemap : createDefaultRemap(target));
|
|
473
|
+
const normalizedSlotRemap = sanitizeRemap(slotRemapSource, target);
|
|
474
|
+
const inputId = slot.inputId !== void 0 && slot.inputId !== null ? slot.inputId : index === 0 ? binding.inputId ?? null : null;
|
|
475
|
+
const slotValueType = sanitizeSlotValueType(
|
|
476
|
+
slot.valueType,
|
|
477
|
+
targetValueType
|
|
478
|
+
);
|
|
479
|
+
return {
|
|
480
|
+
id: normalizedId,
|
|
481
|
+
alias: normalizedAlias,
|
|
482
|
+
inputId,
|
|
483
|
+
remap: cloneRemap(normalizedSlotRemap),
|
|
484
|
+
valueType: slotValueType
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
const primary = normalizedSlots[0];
|
|
489
|
+
const primaryRemap = sanitizeRemap(primary.remap, target);
|
|
490
|
+
const primaryInputId = primary.inputId === SELF_BINDING_ID ? SELF_BINDING_ID : primary.inputId ?? binding.inputId ?? null;
|
|
491
|
+
const primaryAlias = primaryInputId === SELF_BINDING_ID ? "self" : primary.alias || PRIMARY_SLOT_ALIAS;
|
|
492
|
+
normalizedSlots[0] = {
|
|
493
|
+
...primary,
|
|
494
|
+
id: primary.id || PRIMARY_SLOT_ID,
|
|
495
|
+
alias: primaryAlias,
|
|
496
|
+
inputId: primaryInputId,
|
|
497
|
+
remap: cloneRemap(primaryRemap),
|
|
498
|
+
valueType: sanitizeSlotValueType(primary.valueType, targetValueType)
|
|
499
|
+
};
|
|
500
|
+
normalizedSlots.slice(1).forEach((slot, index) => {
|
|
501
|
+
const slotRemap = sanitizeRemap(slot.remap, target);
|
|
502
|
+
normalizedSlots[index + 1] = {
|
|
503
|
+
...slot,
|
|
504
|
+
id: slot.id || defaultSlotId(index + 1),
|
|
505
|
+
alias: slot.alias || defaultSlotId(index + 1),
|
|
506
|
+
remap: cloneRemap(slotRemap),
|
|
507
|
+
valueType: sanitizeSlotValueType(slot.valueType, targetValueType)
|
|
508
|
+
};
|
|
509
|
+
});
|
|
510
|
+
const rawExpression = typeof binding.expression === "string" ? binding.expression.trim() : "";
|
|
511
|
+
let expression = rawExpression.length > 0 ? rawExpression : normalizedSlots[0].alias;
|
|
512
|
+
expression = rewriteLegacyExpression(expression, aliasReplacements);
|
|
513
|
+
const normalizedBinding = {
|
|
514
|
+
...binding,
|
|
515
|
+
inputId: normalizedSlots[0].inputId ?? null,
|
|
516
|
+
remap: cloneRemap(primaryRemap),
|
|
517
|
+
slots: normalizedSlots,
|
|
518
|
+
expression
|
|
519
|
+
};
|
|
520
|
+
const operators = ensureOperators(normalizedBinding);
|
|
521
|
+
if (operatorsEqual(normalizedBinding.operators, operators)) {
|
|
522
|
+
return normalizedBinding;
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
...normalizedBinding,
|
|
526
|
+
operators
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function createDefaultInputValues(inputs = []) {
|
|
530
|
+
const values = {};
|
|
531
|
+
inputs.forEach((input) => {
|
|
532
|
+
values[input.id] = input.defaultValue;
|
|
533
|
+
});
|
|
534
|
+
return values;
|
|
535
|
+
}
|
|
536
|
+
function ensureBindingStructure(binding, target) {
|
|
537
|
+
return ensurePrimarySlot(binding, target);
|
|
538
|
+
}
|
|
539
|
+
function getPrimaryBindingSlot(binding) {
|
|
540
|
+
if (!binding.slots || binding.slots.length === 0) {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
return binding.slots[0];
|
|
544
|
+
}
|
|
545
|
+
function addBindingSlot(binding, target) {
|
|
546
|
+
const base = ensurePrimarySlot(binding, target);
|
|
547
|
+
const nextIndex = base.slots.length + 1;
|
|
548
|
+
const slotId = defaultSlotId(nextIndex - 1);
|
|
549
|
+
const alias = slotId;
|
|
550
|
+
const remap = createDefaultRemap(target);
|
|
551
|
+
const nextSlots = [
|
|
552
|
+
...base.slots,
|
|
553
|
+
{
|
|
554
|
+
id: slotId,
|
|
555
|
+
alias,
|
|
556
|
+
inputId: null,
|
|
557
|
+
remap: cloneRemap(remap)
|
|
558
|
+
}
|
|
559
|
+
];
|
|
560
|
+
return ensurePrimarySlot(
|
|
561
|
+
{
|
|
562
|
+
...base,
|
|
563
|
+
slots: nextSlots
|
|
564
|
+
},
|
|
565
|
+
target
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
function removeBindingSlot(binding, target, slotId) {
|
|
569
|
+
const base = ensurePrimarySlot(binding, target);
|
|
570
|
+
if (base.slots.length <= 1) {
|
|
571
|
+
return base;
|
|
572
|
+
}
|
|
573
|
+
const nextSlots = base.slots.filter((slot) => slot.id !== slotId);
|
|
574
|
+
if (nextSlots.length === base.slots.length) {
|
|
575
|
+
return base;
|
|
576
|
+
}
|
|
577
|
+
const nextBinding = ensurePrimarySlot(
|
|
578
|
+
{
|
|
579
|
+
...base,
|
|
580
|
+
slots: nextSlots
|
|
581
|
+
},
|
|
582
|
+
target
|
|
583
|
+
);
|
|
584
|
+
if (!nextBinding.expression) {
|
|
585
|
+
return nextBinding;
|
|
586
|
+
}
|
|
587
|
+
const hasExpressionAlias = nextBinding.slots.some(
|
|
588
|
+
(slot) => slot.alias === nextBinding.expression
|
|
589
|
+
);
|
|
590
|
+
if (!hasExpressionAlias) {
|
|
591
|
+
return {
|
|
592
|
+
...nextBinding,
|
|
593
|
+
expression: nextBinding.slots[0]?.alias ?? PRIMARY_SLOT_ALIAS
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
return nextBinding;
|
|
597
|
+
}
|
|
598
|
+
function updateBindingSlotAlias(binding, target, slotId, nextAlias) {
|
|
599
|
+
const base = ensurePrimarySlot(binding, target);
|
|
600
|
+
const slotIndex = base.slots.findIndex((slot) => slot.id === slotId);
|
|
601
|
+
if (slotIndex < 0) {
|
|
602
|
+
return base;
|
|
603
|
+
}
|
|
604
|
+
const slots = base.slots.map((slot) => ({ ...slot }));
|
|
605
|
+
const currentSlot = slots[slotIndex];
|
|
606
|
+
const fallbackAlias = currentSlot.alias || currentSlot.id || defaultSlotId(slotIndex);
|
|
607
|
+
const sanitized = sanitizeAliasInput(nextAlias, fallbackAlias, slotIndex);
|
|
608
|
+
const existingAliases = /* @__PURE__ */ new Set();
|
|
609
|
+
slots.forEach((slot, index) => {
|
|
610
|
+
if (index === slotIndex) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (slot.alias) {
|
|
614
|
+
existingAliases.add(slot.alias.toLowerCase());
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
const uniqueAlias = ensureUniqueAlias(sanitized, existingAliases);
|
|
618
|
+
const previousAlias = currentSlot.alias;
|
|
619
|
+
slots[slotIndex] = {
|
|
620
|
+
...currentSlot,
|
|
621
|
+
alias: uniqueAlias
|
|
622
|
+
};
|
|
623
|
+
let nextExpression = base.expression;
|
|
624
|
+
if (previousAlias && previousAlias !== uniqueAlias && typeof nextExpression === "string") {
|
|
625
|
+
const pattern = new RegExp(`\\b${escapeRegExp(previousAlias)}\\b`, "g");
|
|
626
|
+
nextExpression = nextExpression.replace(pattern, uniqueAlias);
|
|
627
|
+
}
|
|
628
|
+
const updated = ensurePrimarySlot(
|
|
629
|
+
{
|
|
630
|
+
...base,
|
|
631
|
+
slots,
|
|
632
|
+
expression: nextExpression
|
|
633
|
+
},
|
|
634
|
+
target
|
|
635
|
+
);
|
|
636
|
+
return updated;
|
|
637
|
+
}
|
|
638
|
+
function setBindingOperatorEnabled(binding, type, enabled) {
|
|
639
|
+
const { binding: normalizedBinding, operators } = normalizeBindingWithOperators(binding);
|
|
640
|
+
let changed = false;
|
|
641
|
+
const nextOperators = operators.map((operator) => {
|
|
642
|
+
if (operator.type !== type) {
|
|
643
|
+
return operator;
|
|
644
|
+
}
|
|
645
|
+
if (operator.enabled === enabled) {
|
|
646
|
+
return operator;
|
|
647
|
+
}
|
|
648
|
+
changed = true;
|
|
649
|
+
return {
|
|
650
|
+
...operator,
|
|
651
|
+
enabled
|
|
652
|
+
};
|
|
653
|
+
});
|
|
654
|
+
if (!changed) {
|
|
655
|
+
return normalizedBinding;
|
|
656
|
+
}
|
|
657
|
+
if (operatorsEqual(normalizedBinding.operators, nextOperators)) {
|
|
658
|
+
return normalizedBinding;
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
...normalizedBinding,
|
|
662
|
+
operators: nextOperators
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function updateBindingOperatorParam(binding, type, paramId, value) {
|
|
666
|
+
const { binding: normalizedBinding, operators } = normalizeBindingWithOperators(binding);
|
|
667
|
+
const definition = getBindingOperatorDefinition(type);
|
|
668
|
+
const paramDefinition = definition.params.find(
|
|
669
|
+
(param) => param.id === paramId
|
|
670
|
+
);
|
|
671
|
+
if (!paramDefinition) {
|
|
672
|
+
return normalizedBinding;
|
|
673
|
+
}
|
|
674
|
+
let nextValue = value;
|
|
675
|
+
if (typeof paramDefinition.min === "number") {
|
|
676
|
+
nextValue = Math.max(paramDefinition.min, nextValue);
|
|
677
|
+
}
|
|
678
|
+
if (typeof paramDefinition.max === "number") {
|
|
679
|
+
nextValue = Math.min(paramDefinition.max, nextValue);
|
|
680
|
+
}
|
|
681
|
+
let changed = false;
|
|
682
|
+
const nextOperators = operators.map((operator) => {
|
|
683
|
+
if (operator.type !== type) {
|
|
684
|
+
return operator;
|
|
685
|
+
}
|
|
686
|
+
if (operator.params[paramId] === nextValue) {
|
|
687
|
+
return operator;
|
|
688
|
+
}
|
|
689
|
+
changed = true;
|
|
690
|
+
return {
|
|
691
|
+
...operator,
|
|
692
|
+
params: {
|
|
693
|
+
...operator.params,
|
|
694
|
+
[paramId]: nextValue
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
});
|
|
698
|
+
if (!changed) {
|
|
699
|
+
return normalizedBinding;
|
|
700
|
+
}
|
|
701
|
+
if (operatorsEqual(normalizedBinding.operators, nextOperators)) {
|
|
702
|
+
return normalizedBinding;
|
|
703
|
+
}
|
|
704
|
+
return {
|
|
705
|
+
...normalizedBinding,
|
|
706
|
+
operators: nextOperators
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
function updateBindingExpression(binding, target, expression) {
|
|
710
|
+
const base = ensurePrimarySlot(binding, target);
|
|
711
|
+
const trimmed = expression.trim();
|
|
712
|
+
return {
|
|
713
|
+
...base,
|
|
714
|
+
expression: trimmed.length > 0 ? trimmed : base.slots[0]?.alias ?? PRIMARY_SLOT_ALIAS
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function updateBindingSlotRemap(binding, target, slotId, field, value) {
|
|
718
|
+
const base = ensurePrimarySlot(binding, target);
|
|
719
|
+
const nextSlots = base.slots.map((slot) => {
|
|
720
|
+
if (slot.id !== slotId) {
|
|
721
|
+
return slot;
|
|
722
|
+
}
|
|
723
|
+
const updatedRemap = {
|
|
724
|
+
...slot.remap,
|
|
725
|
+
[field]: value
|
|
726
|
+
};
|
|
727
|
+
const sanitized = sanitizeRemap(updatedRemap, target);
|
|
728
|
+
return {
|
|
729
|
+
...slot,
|
|
730
|
+
remap: cloneRemap(sanitized)
|
|
731
|
+
};
|
|
732
|
+
});
|
|
733
|
+
const updated = ensurePrimarySlot(
|
|
734
|
+
{
|
|
735
|
+
...base,
|
|
736
|
+
slots: nextSlots
|
|
737
|
+
},
|
|
738
|
+
target
|
|
739
|
+
);
|
|
740
|
+
if (updated.slots[0]?.id === slotId) {
|
|
741
|
+
updated.remap = {
|
|
742
|
+
...updated.remap,
|
|
743
|
+
[field]: value
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
return updated;
|
|
747
|
+
}
|
|
748
|
+
function updateBindingWithInput(binding, target, input, slotId = PRIMARY_SLOT_ID) {
|
|
749
|
+
const base = ensurePrimarySlot(binding, target);
|
|
750
|
+
const slotIndex = base.slots.findIndex((slot) => slot.id === slotId);
|
|
751
|
+
const effectiveIndex = slotIndex >= 0 ? slotIndex : base.slots.length;
|
|
752
|
+
const slots = base.slots.map((slot) => ({
|
|
753
|
+
...slot,
|
|
754
|
+
remap: cloneRemap(slot.remap)
|
|
755
|
+
}));
|
|
756
|
+
if (slotIndex === -1) {
|
|
757
|
+
const alias = slotId === PRIMARY_SLOT_ID && slots.length === 0 ? PRIMARY_SLOT_ALIAS : slotId;
|
|
758
|
+
slots.push({
|
|
759
|
+
id: slotId,
|
|
760
|
+
alias,
|
|
761
|
+
inputId: null,
|
|
762
|
+
remap: cloneRemap(createDefaultRemap(target))
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
const currentSlot = slots[effectiveIndex];
|
|
766
|
+
if (!input) {
|
|
767
|
+
const normalizedSlotRemap = sanitizeRemap(currentSlot.remap, target);
|
|
768
|
+
const updatedRemap2 = {
|
|
769
|
+
...normalizedSlotRemap,
|
|
770
|
+
inLow: DEFAULT_INPUT_RANGE.min,
|
|
771
|
+
inAnchor: DEFAULT_INPUT_ANCHOR,
|
|
772
|
+
inHigh: DEFAULT_INPUT_RANGE.max
|
|
773
|
+
};
|
|
774
|
+
slots[effectiveIndex] = {
|
|
775
|
+
...currentSlot,
|
|
776
|
+
inputId: null,
|
|
777
|
+
remap: cloneRemap(updatedRemap2)
|
|
778
|
+
};
|
|
779
|
+
if (effectiveIndex === 0) {
|
|
780
|
+
return {
|
|
781
|
+
...base,
|
|
782
|
+
inputId: null,
|
|
783
|
+
remap: cloneRemap(updatedRemap2),
|
|
784
|
+
slots
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
...base,
|
|
789
|
+
slots
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
const normalizedRemap = sanitizeRemap(currentSlot.remap, target);
|
|
793
|
+
const updatedRemap = {
|
|
794
|
+
...normalizedRemap,
|
|
795
|
+
inLow: input.range.min,
|
|
796
|
+
inAnchor: clamp(input.defaultValue, input.range.min, input.range.max),
|
|
797
|
+
inHigh: input.range.max,
|
|
798
|
+
...deriveOutputDefaults(target)
|
|
799
|
+
};
|
|
800
|
+
slots[effectiveIndex] = {
|
|
801
|
+
...currentSlot,
|
|
802
|
+
inputId: input.id,
|
|
803
|
+
remap: cloneRemap(updatedRemap)
|
|
804
|
+
};
|
|
805
|
+
if (effectiveIndex === 0) {
|
|
806
|
+
return {
|
|
807
|
+
...base,
|
|
808
|
+
inputId: input.id,
|
|
809
|
+
remap: cloneRemap(updatedRemap),
|
|
810
|
+
slots
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
return {
|
|
814
|
+
...base,
|
|
815
|
+
slots
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
function remapValue(value, remap) {
|
|
819
|
+
const { inLow, inAnchor, inHigh, outLow, outAnchor, outHigh } = remap;
|
|
820
|
+
if (Number.isNaN(value)) {
|
|
821
|
+
return outAnchor;
|
|
822
|
+
}
|
|
823
|
+
if (value <= inAnchor) {
|
|
824
|
+
const span2 = inAnchor - inLow;
|
|
825
|
+
if (Math.abs(span2) < EPSILON) {
|
|
826
|
+
return outLow;
|
|
827
|
+
}
|
|
828
|
+
const t2 = (value - inLow) / span2;
|
|
829
|
+
return outLow + t2 * (outAnchor - outLow);
|
|
830
|
+
}
|
|
831
|
+
const span = inHigh - inAnchor;
|
|
832
|
+
if (Math.abs(span) < EPSILON) {
|
|
833
|
+
return outHigh;
|
|
834
|
+
}
|
|
835
|
+
const t = (value - inAnchor) / span;
|
|
836
|
+
return outAnchor + t * (outHigh - outAnchor);
|
|
837
|
+
}
|
|
838
|
+
function reconcileBindings(previous, components) {
|
|
839
|
+
const next = {};
|
|
840
|
+
components.forEach((component) => {
|
|
841
|
+
const existing = previous[component.id];
|
|
842
|
+
if (existing) {
|
|
843
|
+
const ensured = ensureBindingStructure(existing, component);
|
|
844
|
+
const aliasReplacements = /* @__PURE__ */ new Map();
|
|
845
|
+
const slots = ensured.slots.map((slot, index) => {
|
|
846
|
+
const normalizedId = normalizeSlotId(slot.id, index);
|
|
847
|
+
const { alias: normalizedAlias, replaced } = normalizeSlotAlias(
|
|
848
|
+
slot.alias,
|
|
849
|
+
normalizedId,
|
|
850
|
+
index
|
|
851
|
+
);
|
|
852
|
+
if (replaced && replaced !== normalizedAlias) {
|
|
853
|
+
aliasReplacements.set(replaced, normalizedAlias);
|
|
854
|
+
}
|
|
855
|
+
const slotRemap = sanitizeRemap(slot.remap, component);
|
|
856
|
+
return {
|
|
857
|
+
...slot,
|
|
858
|
+
id: normalizedId,
|
|
859
|
+
alias: normalizedAlias,
|
|
860
|
+
remap: cloneRemap(slotRemap)
|
|
861
|
+
};
|
|
862
|
+
});
|
|
863
|
+
const primary = slots[0];
|
|
864
|
+
let expression = typeof ensured.expression === "string" && ensured.expression.trim().length > 0 ? ensured.expression.trim() : primary.alias;
|
|
865
|
+
expression = rewriteLegacyExpression(expression, aliasReplacements);
|
|
866
|
+
next[component.id] = {
|
|
867
|
+
...ensured,
|
|
868
|
+
targetId: component.id,
|
|
869
|
+
inputId: primary.inputId ?? null,
|
|
870
|
+
remap: cloneRemap(primary.remap),
|
|
871
|
+
slots,
|
|
872
|
+
expression
|
|
873
|
+
};
|
|
874
|
+
} else {
|
|
875
|
+
next[component.id] = createDefaultBinding(component);
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
return next;
|
|
879
|
+
}
|
|
880
|
+
function bindingToDefinition(binding) {
|
|
881
|
+
const operators = binding.operators ? binding.operators.map((operator) => ({
|
|
882
|
+
type: operator.type,
|
|
883
|
+
enabled: !!operator.enabled,
|
|
884
|
+
params: { ...operator.params }
|
|
885
|
+
})) : void 0;
|
|
886
|
+
const definition = {
|
|
887
|
+
inputId: binding.inputId ?? null,
|
|
888
|
+
remap: cloneRemap(binding.remap),
|
|
889
|
+
slots: binding.slots.map((slot) => ({
|
|
890
|
+
...slot,
|
|
891
|
+
remap: cloneRemap(slot.remap)
|
|
892
|
+
})),
|
|
893
|
+
expression: binding.expression,
|
|
894
|
+
operators
|
|
895
|
+
};
|
|
896
|
+
return definition;
|
|
897
|
+
}
|
|
898
|
+
function bindingFromDefinition(target, definition) {
|
|
899
|
+
if (!definition) {
|
|
900
|
+
return createDefaultBinding(target);
|
|
901
|
+
}
|
|
902
|
+
const definitionOperators = definition.operators;
|
|
903
|
+
const binding = {
|
|
904
|
+
targetId: target.id,
|
|
905
|
+
inputId: definition.inputId ?? null,
|
|
906
|
+
remap: cloneRemap(definition.remap),
|
|
907
|
+
slots: definition.slots.map((slot) => ({
|
|
908
|
+
...slot,
|
|
909
|
+
remap: cloneRemap(slot.remap)
|
|
910
|
+
})),
|
|
911
|
+
expression: definition.expression,
|
|
912
|
+
operators: definitionOperators ? definitionOperators.map((operator) => ({
|
|
913
|
+
type: operator.type,
|
|
914
|
+
enabled: !!operator.enabled,
|
|
915
|
+
params: { ...operator.params }
|
|
916
|
+
})) : void 0
|
|
917
|
+
};
|
|
918
|
+
return ensureBindingStructure(binding, target);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/expression.ts
|
|
922
|
+
var WHITESPACE = /\s/;
|
|
923
|
+
var IDENT_START = /[A-Za-z_]/;
|
|
924
|
+
var IDENT_PART = /[A-Za-z0-9_]/;
|
|
925
|
+
var DIGIT = /[0-9]/;
|
|
926
|
+
var ControlExpressionParser = class {
|
|
927
|
+
constructor(input) {
|
|
928
|
+
this.input = input;
|
|
929
|
+
this.index = 0;
|
|
930
|
+
this.errors = [];
|
|
931
|
+
}
|
|
932
|
+
parse() {
|
|
933
|
+
this.skipWhitespace();
|
|
934
|
+
const node = this.parseExpression();
|
|
935
|
+
this.skipWhitespace();
|
|
936
|
+
if (!node) {
|
|
937
|
+
if (this.errors.length === 0) {
|
|
938
|
+
this.errors.push({
|
|
939
|
+
index: this.index,
|
|
940
|
+
message: "Empty expression."
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
return { node: null, errors: this.errors };
|
|
944
|
+
}
|
|
945
|
+
if (!this.isAtEnd()) {
|
|
946
|
+
this.errors.push({
|
|
947
|
+
index: this.index,
|
|
948
|
+
message: `Unexpected token "${this.peek()}"`
|
|
949
|
+
});
|
|
950
|
+
return { node: null, errors: this.errors };
|
|
951
|
+
}
|
|
952
|
+
return { node, errors: this.errors };
|
|
953
|
+
}
|
|
954
|
+
parseExpression() {
|
|
955
|
+
return this.parseLogicalOr();
|
|
956
|
+
}
|
|
957
|
+
parseLogicalOr() {
|
|
958
|
+
let left = this.parseLogicalAnd();
|
|
959
|
+
if (!left) {
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
while (true) {
|
|
963
|
+
this.skipWhitespace();
|
|
964
|
+
const operator = this.matchAny(["||"]);
|
|
965
|
+
if (!operator) {
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
const right = this.parseLogicalAnd();
|
|
969
|
+
if (!right) {
|
|
970
|
+
this.errors.push({
|
|
971
|
+
index: this.index,
|
|
972
|
+
message: "Expected expression after operator."
|
|
973
|
+
});
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
left = {
|
|
977
|
+
type: "Binary",
|
|
978
|
+
operator,
|
|
979
|
+
left,
|
|
980
|
+
right
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
return left;
|
|
984
|
+
}
|
|
985
|
+
parseLogicalAnd() {
|
|
986
|
+
let left = this.parseComparison();
|
|
987
|
+
if (!left) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
while (true) {
|
|
991
|
+
this.skipWhitespace();
|
|
992
|
+
const operator = this.matchAny(["&&"]);
|
|
993
|
+
if (!operator) {
|
|
994
|
+
break;
|
|
995
|
+
}
|
|
996
|
+
const right = this.parseComparison();
|
|
997
|
+
if (!right) {
|
|
998
|
+
this.errors.push({
|
|
999
|
+
index: this.index,
|
|
1000
|
+
message: "Expected expression after operator."
|
|
1001
|
+
});
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
left = {
|
|
1005
|
+
type: "Binary",
|
|
1006
|
+
operator,
|
|
1007
|
+
left,
|
|
1008
|
+
right
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
return left;
|
|
1012
|
+
}
|
|
1013
|
+
parseComparison() {
|
|
1014
|
+
let left = this.parseAdditive();
|
|
1015
|
+
if (!left) {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
while (true) {
|
|
1019
|
+
this.skipWhitespace();
|
|
1020
|
+
if (this.input.startsWith(">=", this.index) || this.input.startsWith("<=", this.index)) {
|
|
1021
|
+
const op = this.input.slice(this.index, this.index + 2);
|
|
1022
|
+
this.index += 2;
|
|
1023
|
+
this.errors.push({
|
|
1024
|
+
index: this.index - 2,
|
|
1025
|
+
message: `Operator "${op}" is not supported.`
|
|
1026
|
+
});
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
const operator = this.matchAny(["==", "!=", ">", "<"]);
|
|
1030
|
+
if (!operator) {
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
1033
|
+
const right = this.parseAdditive();
|
|
1034
|
+
if (!right) {
|
|
1035
|
+
this.errors.push({
|
|
1036
|
+
index: this.index,
|
|
1037
|
+
message: "Expected expression after operator."
|
|
1038
|
+
});
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
left = {
|
|
1042
|
+
type: "Binary",
|
|
1043
|
+
operator,
|
|
1044
|
+
left,
|
|
1045
|
+
right
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
return left;
|
|
1049
|
+
}
|
|
1050
|
+
parseAdditive() {
|
|
1051
|
+
let left = this.parseMultiplicative();
|
|
1052
|
+
if (!left) {
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
while (true) {
|
|
1056
|
+
this.skipWhitespace();
|
|
1057
|
+
const operator = this.peek();
|
|
1058
|
+
if (operator !== "+" && operator !== "-") {
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
this.index += 1;
|
|
1062
|
+
const right = this.parseMultiplicative();
|
|
1063
|
+
if (!right) {
|
|
1064
|
+
this.errors.push({
|
|
1065
|
+
index: this.index,
|
|
1066
|
+
message: "Expected expression after operator."
|
|
1067
|
+
});
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
left = {
|
|
1071
|
+
type: "Binary",
|
|
1072
|
+
operator,
|
|
1073
|
+
left,
|
|
1074
|
+
right
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
return left;
|
|
1078
|
+
}
|
|
1079
|
+
parseMultiplicative() {
|
|
1080
|
+
let left = this.parseUnary();
|
|
1081
|
+
if (!left) {
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
while (true) {
|
|
1085
|
+
this.skipWhitespace();
|
|
1086
|
+
const operator = this.peek();
|
|
1087
|
+
if (operator !== "*" && operator !== "/") {
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
this.index += 1;
|
|
1091
|
+
const right = this.parseUnary();
|
|
1092
|
+
if (!right) {
|
|
1093
|
+
this.errors.push({
|
|
1094
|
+
index: this.index,
|
|
1095
|
+
message: "Expected expression after operator."
|
|
1096
|
+
});
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
left = {
|
|
1100
|
+
type: "Binary",
|
|
1101
|
+
operator,
|
|
1102
|
+
left,
|
|
1103
|
+
right
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
return left;
|
|
1107
|
+
}
|
|
1108
|
+
parseUnary() {
|
|
1109
|
+
this.skipWhitespace();
|
|
1110
|
+
const char = this.peek();
|
|
1111
|
+
if (!char) {
|
|
1112
|
+
this.errors.push({
|
|
1113
|
+
index: this.index,
|
|
1114
|
+
message: "Unexpected end of expression."
|
|
1115
|
+
});
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
if (char === "+" || char === "-" || char === "!") {
|
|
1119
|
+
this.index += 1;
|
|
1120
|
+
const operand = this.parseUnary();
|
|
1121
|
+
if (!operand) {
|
|
1122
|
+
this.errors.push({
|
|
1123
|
+
index: this.index,
|
|
1124
|
+
message: `Expected operand after unary "${char}".`
|
|
1125
|
+
});
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1128
|
+
return {
|
|
1129
|
+
type: "Unary",
|
|
1130
|
+
operator: char,
|
|
1131
|
+
operand
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
return this.parsePrimary();
|
|
1135
|
+
}
|
|
1136
|
+
parsePrimary() {
|
|
1137
|
+
this.skipWhitespace();
|
|
1138
|
+
const char = this.peek();
|
|
1139
|
+
if (!char) {
|
|
1140
|
+
this.errors.push({
|
|
1141
|
+
index: this.index,
|
|
1142
|
+
message: "Unexpected end of expression."
|
|
1143
|
+
});
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
if (char === "(") {
|
|
1147
|
+
this.index += 1;
|
|
1148
|
+
const expression = this.parseExpression();
|
|
1149
|
+
this.skipWhitespace();
|
|
1150
|
+
if (this.peek() === ")") {
|
|
1151
|
+
this.index += 1;
|
|
1152
|
+
return expression;
|
|
1153
|
+
}
|
|
1154
|
+
this.errors.push({
|
|
1155
|
+
index: this.index,
|
|
1156
|
+
message: "Unmatched parenthesis."
|
|
1157
|
+
});
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1160
|
+
if (IDENT_START.test(char)) {
|
|
1161
|
+
return this.parseIdentifierOrFunction();
|
|
1162
|
+
}
|
|
1163
|
+
if (DIGIT.test(char) || char === ".") {
|
|
1164
|
+
return this.parseNumber();
|
|
1165
|
+
}
|
|
1166
|
+
this.errors.push({
|
|
1167
|
+
index: this.index,
|
|
1168
|
+
message: `Unexpected character "${char}".`
|
|
1169
|
+
});
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
1172
|
+
parseIdentifierOrFunction() {
|
|
1173
|
+
const start = this.index;
|
|
1174
|
+
while (!this.isAtEnd() && IDENT_PART.test(this.peek())) {
|
|
1175
|
+
this.index += 1;
|
|
1176
|
+
}
|
|
1177
|
+
const name = this.input.slice(start, this.index);
|
|
1178
|
+
if (!name) {
|
|
1179
|
+
this.errors.push({
|
|
1180
|
+
index: start,
|
|
1181
|
+
message: "Invalid identifier."
|
|
1182
|
+
});
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
this.skipWhitespace();
|
|
1186
|
+
if (this.peek() === "(") {
|
|
1187
|
+
this.index += 1;
|
|
1188
|
+
const args = [];
|
|
1189
|
+
this.skipWhitespace();
|
|
1190
|
+
if (this.peek() === ")") {
|
|
1191
|
+
this.index += 1;
|
|
1192
|
+
return {
|
|
1193
|
+
type: "Function",
|
|
1194
|
+
name,
|
|
1195
|
+
args
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
while (true) {
|
|
1199
|
+
const argument = this.parseExpression();
|
|
1200
|
+
if (!argument) {
|
|
1201
|
+
this.errors.push({
|
|
1202
|
+
index: this.index,
|
|
1203
|
+
message: `Expected expression for argument ${args.length + 1} of "${name}".`
|
|
1204
|
+
});
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
args.push(argument);
|
|
1208
|
+
this.skipWhitespace();
|
|
1209
|
+
const next = this.peek();
|
|
1210
|
+
if (next === ",") {
|
|
1211
|
+
this.index += 1;
|
|
1212
|
+
this.skipWhitespace();
|
|
1213
|
+
if (this.peek() === ")") {
|
|
1214
|
+
this.errors.push({
|
|
1215
|
+
index: this.index,
|
|
1216
|
+
message: `Expected expression after "," in call to "${name}".`
|
|
1217
|
+
});
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
continue;
|
|
1221
|
+
}
|
|
1222
|
+
if (next === ")") {
|
|
1223
|
+
this.index += 1;
|
|
1224
|
+
break;
|
|
1225
|
+
}
|
|
1226
|
+
if (next === null) {
|
|
1227
|
+
this.errors.push({
|
|
1228
|
+
index: this.index,
|
|
1229
|
+
message: `Unterminated call to "${name}".`
|
|
1230
|
+
});
|
|
1231
|
+
} else {
|
|
1232
|
+
this.errors.push({
|
|
1233
|
+
index: this.index,
|
|
1234
|
+
message: `Expected "," or ")" in call to "${name}".`
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
return {
|
|
1240
|
+
type: "Function",
|
|
1241
|
+
name,
|
|
1242
|
+
args
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
return {
|
|
1246
|
+
type: "Reference",
|
|
1247
|
+
name
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
parseNumber() {
|
|
1251
|
+
const start = this.index;
|
|
1252
|
+
let hasDigits = false;
|
|
1253
|
+
while (!this.isAtEnd()) {
|
|
1254
|
+
const char = this.peek();
|
|
1255
|
+
if (DIGIT.test(char)) {
|
|
1256
|
+
hasDigits = true;
|
|
1257
|
+
this.index += 1;
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
if (char === ".") {
|
|
1261
|
+
this.index += 1;
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
break;
|
|
1265
|
+
}
|
|
1266
|
+
const raw = this.input.slice(start, this.index);
|
|
1267
|
+
const value = Number(raw);
|
|
1268
|
+
if (!hasDigits || Number.isNaN(value)) {
|
|
1269
|
+
this.errors.push({
|
|
1270
|
+
index: start,
|
|
1271
|
+
message: `Invalid numeric literal "${raw}".`
|
|
1272
|
+
});
|
|
1273
|
+
return null;
|
|
1274
|
+
}
|
|
1275
|
+
return {
|
|
1276
|
+
type: "Literal",
|
|
1277
|
+
value
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
matchAny(operators) {
|
|
1281
|
+
for (const operator of operators) {
|
|
1282
|
+
if (this.input.startsWith(operator, this.index)) {
|
|
1283
|
+
this.index += operator.length;
|
|
1284
|
+
return operator;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
skipWhitespace() {
|
|
1290
|
+
while (!this.isAtEnd() && WHITESPACE.test(this.peek())) {
|
|
1291
|
+
this.index += 1;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
peek() {
|
|
1295
|
+
if (this.index >= this.input.length) {
|
|
1296
|
+
return null;
|
|
1297
|
+
}
|
|
1298
|
+
return this.input[this.index] ?? null;
|
|
1299
|
+
}
|
|
1300
|
+
isAtEnd() {
|
|
1301
|
+
return this.index >= this.input.length;
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
function parseControlExpression(expression) {
|
|
1305
|
+
const parser = new ControlExpressionParser(expression);
|
|
1306
|
+
return parser.parse();
|
|
1307
|
+
}
|
|
1308
|
+
function collectExpressionReferences(node, target = /* @__PURE__ */ new Set()) {
|
|
1309
|
+
if (!node) {
|
|
1310
|
+
return target;
|
|
1311
|
+
}
|
|
1312
|
+
switch (node.type) {
|
|
1313
|
+
case "Reference":
|
|
1314
|
+
target.add(node.name);
|
|
1315
|
+
break;
|
|
1316
|
+
case "Unary":
|
|
1317
|
+
collectExpressionReferences(node.operand, target);
|
|
1318
|
+
break;
|
|
1319
|
+
case "Binary":
|
|
1320
|
+
collectExpressionReferences(node.left, target);
|
|
1321
|
+
collectExpressionReferences(node.right, target);
|
|
1322
|
+
break;
|
|
1323
|
+
case "Function":
|
|
1324
|
+
node.args.forEach((arg) => collectExpressionReferences(arg, target));
|
|
1325
|
+
break;
|
|
1326
|
+
default:
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
return target;
|
|
1330
|
+
}
|
|
1331
|
+
function mapExpression(node, visit) {
|
|
1332
|
+
visit(node);
|
|
1333
|
+
switch (node.type) {
|
|
1334
|
+
case "Unary":
|
|
1335
|
+
mapExpression(node.operand, visit);
|
|
1336
|
+
break;
|
|
1337
|
+
case "Binary":
|
|
1338
|
+
mapExpression(node.left, visit);
|
|
1339
|
+
mapExpression(node.right, visit);
|
|
1340
|
+
break;
|
|
1341
|
+
case "Function":
|
|
1342
|
+
node.args.forEach((arg) => mapExpression(arg, visit));
|
|
1343
|
+
break;
|
|
1344
|
+
default:
|
|
1345
|
+
break;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// src/expressionFunctions.ts
|
|
1350
|
+
import { requireNodeSignature as requireNodeSignature2 } from "@vizij/node-graph-wasm/metadata";
|
|
1351
|
+
var FUNCTION_CONFIGS = [
|
|
1352
|
+
{ typeId: "sin", names: ["sin"] },
|
|
1353
|
+
{ typeId: "cos", names: ["cos"] },
|
|
1354
|
+
{ typeId: "tan", names: ["tan"] },
|
|
1355
|
+
{ typeId: "power", names: ["power", "pow"] },
|
|
1356
|
+
{ typeId: "log", names: ["log"] },
|
|
1357
|
+
{ typeId: "clamp", names: ["clamp"] },
|
|
1358
|
+
{ typeId: "add", names: ["add"] },
|
|
1359
|
+
{ typeId: "multiply", names: ["multiply"] },
|
|
1360
|
+
{ typeId: "subtract", names: ["subtract"] },
|
|
1361
|
+
{ typeId: "divide", names: ["divide"] },
|
|
1362
|
+
{
|
|
1363
|
+
typeId: "greaterthan",
|
|
1364
|
+
names: ["greaterthan", "gt"],
|
|
1365
|
+
minArgs: 2,
|
|
1366
|
+
maxArgs: 2
|
|
1367
|
+
},
|
|
1368
|
+
{ typeId: "lessthan", names: ["lessthan", "lt"], minArgs: 2, maxArgs: 2 },
|
|
1369
|
+
{ typeId: "equal", names: ["equal", "eq"], minArgs: 2, maxArgs: 2 },
|
|
1370
|
+
{
|
|
1371
|
+
typeId: "notequal",
|
|
1372
|
+
names: ["notequal", "neq", "ne"],
|
|
1373
|
+
minArgs: 2,
|
|
1374
|
+
maxArgs: 2
|
|
1375
|
+
},
|
|
1376
|
+
{ typeId: "and", names: ["and"], minArgs: 2, maxArgs: 2 },
|
|
1377
|
+
{ typeId: "or", names: ["or"], minArgs: 2, maxArgs: 2 },
|
|
1378
|
+
{ typeId: "xor", names: ["xor"], minArgs: 2, maxArgs: 2 },
|
|
1379
|
+
{ typeId: "not", names: ["not"], minArgs: 1, maxArgs: 1 },
|
|
1380
|
+
{ typeId: "if", names: ["if"], minArgs: 2, maxArgs: 3 },
|
|
1381
|
+
{ typeId: "time", names: ["time"], minArgs: 0, maxArgs: 0 },
|
|
1382
|
+
{ typeId: "oscillator", names: ["oscillator"], minArgs: 2, maxArgs: 2 }
|
|
1383
|
+
];
|
|
1384
|
+
var SCALAR_FUNCTIONS = /* @__PURE__ */ new Map();
|
|
1385
|
+
for (const config of FUNCTION_CONFIGS) {
|
|
1386
|
+
const signature = requireNodeSignature2(config.typeId);
|
|
1387
|
+
const signatureInputs = signature.inputs;
|
|
1388
|
+
const inputs = signatureInputs.map((input) => ({
|
|
1389
|
+
id: input.id,
|
|
1390
|
+
optional: Boolean(input.optional)
|
|
1391
|
+
}));
|
|
1392
|
+
const variadic = signature.variadic_inputs ? {
|
|
1393
|
+
id: signature.variadic_inputs.id,
|
|
1394
|
+
min: signature.variadic_inputs.min,
|
|
1395
|
+
max: signature.variadic_inputs.max ?? null
|
|
1396
|
+
} : null;
|
|
1397
|
+
const derivedMin = variadic ? variadic.min : inputs.filter((input) => !input.optional).length;
|
|
1398
|
+
const derivedMax = variadic ? variadic.max : inputs.length;
|
|
1399
|
+
const minArgs = config.minArgs ?? derivedMin;
|
|
1400
|
+
const maxArgs = config.maxArgs !== void 0 ? config.maxArgs : derivedMax ?? null;
|
|
1401
|
+
const definition = {
|
|
1402
|
+
nodeType: config.typeId,
|
|
1403
|
+
inputs,
|
|
1404
|
+
variadic,
|
|
1405
|
+
minArgs,
|
|
1406
|
+
maxArgs
|
|
1407
|
+
};
|
|
1408
|
+
const names = new Set(
|
|
1409
|
+
[config.typeId, ...config.names].map((name) => name.toLowerCase())
|
|
1410
|
+
);
|
|
1411
|
+
names.forEach((name) => {
|
|
1412
|
+
SCALAR_FUNCTIONS.set(name, definition);
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// src/graphBuilder.ts
|
|
1417
|
+
function evaluateBinding({
|
|
1418
|
+
binding,
|
|
1419
|
+
target,
|
|
1420
|
+
targetId,
|
|
1421
|
+
animatableId,
|
|
1422
|
+
component,
|
|
1423
|
+
safeId,
|
|
1424
|
+
context,
|
|
1425
|
+
selfNodeId
|
|
1426
|
+
}) {
|
|
1427
|
+
const { nodes, edges, ensureInputNode, bindingIssues, summaryBindings } = context;
|
|
1428
|
+
const exprContext = {
|
|
1429
|
+
componentSafeId: safeId,
|
|
1430
|
+
nodes,
|
|
1431
|
+
edges,
|
|
1432
|
+
constants: /* @__PURE__ */ new Map(),
|
|
1433
|
+
counter: 0
|
|
1434
|
+
};
|
|
1435
|
+
const targetValueType = target.valueType === "vector" ? "vector" : "scalar";
|
|
1436
|
+
const aliasNodes = /* @__PURE__ */ new Map();
|
|
1437
|
+
const slotSummaries = [];
|
|
1438
|
+
const expressionIssues = [];
|
|
1439
|
+
const rawExpression = typeof binding.expression === "string" ? binding.expression : "";
|
|
1440
|
+
const trimmedExpression = rawExpression.trim();
|
|
1441
|
+
let hasActiveSlot = false;
|
|
1442
|
+
binding.slots.forEach((slot, index) => {
|
|
1443
|
+
const aliasBase = slot.alias?.trim() ?? "";
|
|
1444
|
+
const fallbackAlias = `s${index + 1}`;
|
|
1445
|
+
const alias = aliasBase.length > 0 ? aliasBase : fallbackAlias;
|
|
1446
|
+
const slotId = slot.id && slot.id.length > 0 ? slot.id : alias;
|
|
1447
|
+
const slotValueType = slot.valueType === "vector" ? "vector" : "scalar";
|
|
1448
|
+
let slotOutputId;
|
|
1449
|
+
if (slot.inputId === SELF_BINDING_ID2) {
|
|
1450
|
+
if (selfNodeId) {
|
|
1451
|
+
slotOutputId = selfNodeId;
|
|
1452
|
+
hasActiveSlot = true;
|
|
1453
|
+
} else {
|
|
1454
|
+
expressionIssues.push("Self reference unavailable for this input.");
|
|
1455
|
+
slotOutputId = getConstantNodeId(exprContext, target.defaultValue);
|
|
1456
|
+
}
|
|
1457
|
+
} else if (slot.inputId) {
|
|
1458
|
+
const inputNode = ensureInputNode(slot.inputId);
|
|
1459
|
+
if (inputNode) {
|
|
1460
|
+
const remapNodeId = `remap_${safeId}_${sanitizeNodeId(slotId)}`;
|
|
1461
|
+
nodes.push({
|
|
1462
|
+
id: remapNodeId,
|
|
1463
|
+
type: "centered_remap",
|
|
1464
|
+
input_defaults: {
|
|
1465
|
+
in_low: slot.remap.inLow,
|
|
1466
|
+
in_anchor: slot.remap.inAnchor,
|
|
1467
|
+
in_high: slot.remap.inHigh,
|
|
1468
|
+
out_low: slot.remap.outLow,
|
|
1469
|
+
out_anchor: slot.remap.outAnchor,
|
|
1470
|
+
out_high: slot.remap.outHigh
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
edges.push({
|
|
1474
|
+
from: { node_id: inputNode.nodeId },
|
|
1475
|
+
to: { node_id: remapNodeId, input: "in" }
|
|
1476
|
+
});
|
|
1477
|
+
slotOutputId = remapNodeId;
|
|
1478
|
+
hasActiveSlot = true;
|
|
1479
|
+
} else {
|
|
1480
|
+
expressionIssues.push(`Missing standard input "${slot.inputId}".`);
|
|
1481
|
+
slotOutputId = getConstantNodeId(exprContext, 0);
|
|
1482
|
+
}
|
|
1483
|
+
} else {
|
|
1484
|
+
slotOutputId = getConstantNodeId(exprContext, 0);
|
|
1485
|
+
}
|
|
1486
|
+
aliasNodes.set(alias, slotOutputId);
|
|
1487
|
+
slotSummaries.push({
|
|
1488
|
+
targetId,
|
|
1489
|
+
animatableId,
|
|
1490
|
+
component,
|
|
1491
|
+
slotId,
|
|
1492
|
+
slotAlias: alias,
|
|
1493
|
+
inputId: slot.inputId ?? null,
|
|
1494
|
+
remap: { ...slot.remap },
|
|
1495
|
+
expression: trimmedExpression,
|
|
1496
|
+
valueType: slotValueType
|
|
1497
|
+
});
|
|
1498
|
+
});
|
|
1499
|
+
if (slotSummaries.length === 0) {
|
|
1500
|
+
const alias = PRIMARY_SLOT_ALIAS;
|
|
1501
|
+
aliasNodes.set(alias, getConstantNodeId(exprContext, 0));
|
|
1502
|
+
slotSummaries.push({
|
|
1503
|
+
targetId,
|
|
1504
|
+
animatableId,
|
|
1505
|
+
component,
|
|
1506
|
+
slotId: PRIMARY_SLOT_ID,
|
|
1507
|
+
slotAlias: alias,
|
|
1508
|
+
inputId: null,
|
|
1509
|
+
remap: createDefaultRemap(target),
|
|
1510
|
+
expression: trimmedExpression,
|
|
1511
|
+
valueType: targetValueType
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
const defaultAlias = slotSummaries[0]?.slotAlias ?? PRIMARY_SLOT_ALIAS;
|
|
1515
|
+
const expressionText = trimmedExpression.length > 0 ? trimmedExpression : defaultAlias;
|
|
1516
|
+
const parseResult = parseControlExpression(expressionText);
|
|
1517
|
+
let expressionAst = null;
|
|
1518
|
+
if (parseResult.node && parseResult.errors.length === 0) {
|
|
1519
|
+
const references = collectExpressionReferences(parseResult.node);
|
|
1520
|
+
const missing = [];
|
|
1521
|
+
references.forEach((ref) => {
|
|
1522
|
+
if (!aliasNodes.has(ref)) {
|
|
1523
|
+
missing.push(ref);
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
if (missing.length === 0) {
|
|
1527
|
+
expressionAst = parseResult.node;
|
|
1528
|
+
} else {
|
|
1529
|
+
missing.forEach((ref) => {
|
|
1530
|
+
expressionIssues.push(`Unknown control "${ref}".`);
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
} else {
|
|
1534
|
+
parseResult.errors.forEach((error) => {
|
|
1535
|
+
expressionIssues.push(error.message);
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
let valueNodeId = null;
|
|
1539
|
+
if (expressionAst) {
|
|
1540
|
+
valueNodeId = materializeExpression(
|
|
1541
|
+
expressionAst,
|
|
1542
|
+
exprContext,
|
|
1543
|
+
aliasNodes,
|
|
1544
|
+
expressionIssues
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
if (!valueNodeId) {
|
|
1548
|
+
const fallbackAlias = aliasNodes.has(defaultAlias) ? defaultAlias : aliasNodes.keys().next().value;
|
|
1549
|
+
valueNodeId = (fallbackAlias ? aliasNodes.get(fallbackAlias) : void 0) ?? getConstantNodeId(exprContext, 0);
|
|
1550
|
+
}
|
|
1551
|
+
const issuesCopy = expressionIssues.length ? [...new Set(expressionIssues)] : void 0;
|
|
1552
|
+
slotSummaries.forEach((summary) => {
|
|
1553
|
+
summary.expression = expressionText;
|
|
1554
|
+
if (issuesCopy && issuesCopy.length > 0) {
|
|
1555
|
+
summary.issues = issuesCopy;
|
|
1556
|
+
const issueSet = bindingIssues.get(summary.targetId) ?? /* @__PURE__ */ new Set();
|
|
1557
|
+
issuesCopy.forEach((issue) => issueSet.add(issue));
|
|
1558
|
+
bindingIssues.set(summary.targetId, issueSet);
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
summaryBindings.push(...slotSummaries);
|
|
1562
|
+
return {
|
|
1563
|
+
valueNodeId,
|
|
1564
|
+
hasActiveSlot
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
function sanitizeNodeId(value) {
|
|
1568
|
+
return value.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
1569
|
+
}
|
|
1570
|
+
function buildRigInputPath(faceId, inputPath) {
|
|
1571
|
+
const trimmed = inputPath.startsWith("/") ? inputPath.slice(1) : inputPath;
|
|
1572
|
+
return `rig/${faceId}/${trimmed}`;
|
|
1573
|
+
}
|
|
1574
|
+
function getComponentOrder(animatable) {
|
|
1575
|
+
switch (animatable.type) {
|
|
1576
|
+
case "vector2":
|
|
1577
|
+
return ["x", "y"];
|
|
1578
|
+
case "vector3":
|
|
1579
|
+
case "euler":
|
|
1580
|
+
return ["x", "y", "z"];
|
|
1581
|
+
case "rgb":
|
|
1582
|
+
return ["r", "g", "b"];
|
|
1583
|
+
default:
|
|
1584
|
+
return null;
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
function isComponentRecord(value) {
|
|
1588
|
+
return typeof value === "object" && value !== null;
|
|
1589
|
+
}
|
|
1590
|
+
function componentIndex(component) {
|
|
1591
|
+
switch (component) {
|
|
1592
|
+
case "x":
|
|
1593
|
+
case "r":
|
|
1594
|
+
return 0;
|
|
1595
|
+
case "y":
|
|
1596
|
+
case "g":
|
|
1597
|
+
return 1;
|
|
1598
|
+
default:
|
|
1599
|
+
return 2;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
function extractComponentDefault(value, component) {
|
|
1603
|
+
if (typeof value === "number") {
|
|
1604
|
+
return value;
|
|
1605
|
+
}
|
|
1606
|
+
if (Array.isArray(value)) {
|
|
1607
|
+
const candidate = value[componentIndex(component)];
|
|
1608
|
+
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
1609
|
+
return candidate;
|
|
1610
|
+
}
|
|
1611
|
+
return 0;
|
|
1612
|
+
}
|
|
1613
|
+
if (value && typeof value === "object") {
|
|
1614
|
+
if (!isComponentRecord(value)) {
|
|
1615
|
+
return 0;
|
|
1616
|
+
}
|
|
1617
|
+
const direct = value[component];
|
|
1618
|
+
if (typeof direct === "number" && Number.isFinite(direct)) {
|
|
1619
|
+
return direct;
|
|
1620
|
+
}
|
|
1621
|
+
const alt = component === "x" ? value.r : component === "y" ? value.g : value.b;
|
|
1622
|
+
if (typeof alt === "number" && Number.isFinite(alt)) {
|
|
1623
|
+
return alt;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return 0;
|
|
1627
|
+
}
|
|
1628
|
+
function getConstantNodeId(context, value) {
|
|
1629
|
+
const key = Number.isFinite(value) ? value.toString() : "NaN";
|
|
1630
|
+
const existing = context.constants.get(key);
|
|
1631
|
+
if (existing) {
|
|
1632
|
+
return existing;
|
|
1633
|
+
}
|
|
1634
|
+
const nodeId = `const_${context.componentSafeId}_${context.constants.size + 1}`;
|
|
1635
|
+
context.nodes.push({
|
|
1636
|
+
id: nodeId,
|
|
1637
|
+
type: "constant",
|
|
1638
|
+
params: {
|
|
1639
|
+
value: Number.isFinite(value) ? value : 0
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
context.constants.set(key, nodeId);
|
|
1643
|
+
return nodeId;
|
|
1644
|
+
}
|
|
1645
|
+
function createBinaryOperationNode(context, operator, leftId, rightId) {
|
|
1646
|
+
const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
|
|
1647
|
+
context.nodes.push({
|
|
1648
|
+
id: nodeId,
|
|
1649
|
+
type: operator
|
|
1650
|
+
});
|
|
1651
|
+
const leftInput = operator === "subtract" || operator === "divide" ? "lhs" : "operand_1";
|
|
1652
|
+
const rightInput = operator === "subtract" || operator === "divide" ? "rhs" : "operand_2";
|
|
1653
|
+
context.edges.push({
|
|
1654
|
+
from: { node_id: leftId },
|
|
1655
|
+
to: { node_id: nodeId, input: leftInput }
|
|
1656
|
+
});
|
|
1657
|
+
context.edges.push({
|
|
1658
|
+
from: { node_id: rightId },
|
|
1659
|
+
to: { node_id: nodeId, input: rightInput }
|
|
1660
|
+
});
|
|
1661
|
+
return nodeId;
|
|
1662
|
+
}
|
|
1663
|
+
function createVariadicOperationNode(context, operator, operandIds) {
|
|
1664
|
+
if (operandIds.length === 0) {
|
|
1665
|
+
return getConstantNodeId(context, operator === "add" ? 0 : 1);
|
|
1666
|
+
}
|
|
1667
|
+
if (operandIds.length === 1) {
|
|
1668
|
+
return operandIds[0];
|
|
1669
|
+
}
|
|
1670
|
+
const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
|
|
1671
|
+
context.nodes.push({
|
|
1672
|
+
id: nodeId,
|
|
1673
|
+
type: operator
|
|
1674
|
+
});
|
|
1675
|
+
operandIds.forEach((operandId, index) => {
|
|
1676
|
+
context.edges.push({
|
|
1677
|
+
from: { node_id: operandId },
|
|
1678
|
+
to: { node_id: nodeId, input: `operand_${index + 1}` }
|
|
1679
|
+
});
|
|
1680
|
+
});
|
|
1681
|
+
return nodeId;
|
|
1682
|
+
}
|
|
1683
|
+
function createNamedOperationNode(context, operator, inputNames, operandIds) {
|
|
1684
|
+
const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
|
|
1685
|
+
context.nodes.push({
|
|
1686
|
+
id: nodeId,
|
|
1687
|
+
type: operator
|
|
1688
|
+
});
|
|
1689
|
+
inputNames.forEach((inputName, index) => {
|
|
1690
|
+
const operandId = operandIds[index];
|
|
1691
|
+
context.edges.push({
|
|
1692
|
+
from: { node_id: operandId },
|
|
1693
|
+
to: { node_id: nodeId, input: inputName }
|
|
1694
|
+
});
|
|
1695
|
+
});
|
|
1696
|
+
return nodeId;
|
|
1697
|
+
}
|
|
1698
|
+
function emitScalarFunctionNode(definition, operands, context) {
|
|
1699
|
+
if (definition.variadic) {
|
|
1700
|
+
const nodeId = `expr_${context.componentSafeId}_${context.counter++}`;
|
|
1701
|
+
context.nodes.push({
|
|
1702
|
+
id: nodeId,
|
|
1703
|
+
type: definition.nodeType
|
|
1704
|
+
});
|
|
1705
|
+
operands.forEach((operandId, index) => {
|
|
1706
|
+
context.edges.push({
|
|
1707
|
+
from: { node_id: operandId },
|
|
1708
|
+
to: {
|
|
1709
|
+
node_id: nodeId,
|
|
1710
|
+
input: `${definition.variadic.id}_${index + 1}`
|
|
1711
|
+
}
|
|
1712
|
+
});
|
|
1713
|
+
});
|
|
1714
|
+
return nodeId;
|
|
1715
|
+
}
|
|
1716
|
+
const providedNames = [];
|
|
1717
|
+
const providedOperands = [];
|
|
1718
|
+
definition.inputs.forEach((input, index) => {
|
|
1719
|
+
const operandId = operands[index];
|
|
1720
|
+
if (!operandId) {
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
providedNames.push(input.id);
|
|
1724
|
+
providedOperands.push(operandId);
|
|
1725
|
+
});
|
|
1726
|
+
return createNamedOperationNode(
|
|
1727
|
+
context,
|
|
1728
|
+
definition.nodeType,
|
|
1729
|
+
providedNames,
|
|
1730
|
+
providedOperands
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
function applyBindingOperators(operators, baseNodeId, safeId, nodes, edges) {
|
|
1734
|
+
let currentNodeId = baseNodeId;
|
|
1735
|
+
operators.forEach((operator, index) => {
|
|
1736
|
+
if (!operator.enabled) {
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
1739
|
+
let definition;
|
|
1740
|
+
try {
|
|
1741
|
+
definition = getBindingOperatorDefinition(operator.type);
|
|
1742
|
+
} catch {
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
const nodeId = `${operator.type}_${safeId}_${index + 1}`;
|
|
1746
|
+
const params = {};
|
|
1747
|
+
definition.params.forEach((param) => {
|
|
1748
|
+
const configured = operator.params?.[param.id];
|
|
1749
|
+
params[param.id] = typeof configured === "number" ? configured : param.defaultValue;
|
|
1750
|
+
});
|
|
1751
|
+
nodes.push({
|
|
1752
|
+
id: nodeId,
|
|
1753
|
+
type: definition.nodeType,
|
|
1754
|
+
params
|
|
1755
|
+
});
|
|
1756
|
+
const inputId = definition.inputs[0] ?? "in";
|
|
1757
|
+
edges.push({
|
|
1758
|
+
from: { node_id: currentNodeId },
|
|
1759
|
+
to: { node_id: nodeId, input: inputId }
|
|
1760
|
+
});
|
|
1761
|
+
currentNodeId = nodeId;
|
|
1762
|
+
});
|
|
1763
|
+
return currentNodeId;
|
|
1764
|
+
}
|
|
1765
|
+
function collectOperands(node, operator, target) {
|
|
1766
|
+
if (node.type === "Binary" && node.operator === operator) {
|
|
1767
|
+
collectOperands(node.left, operator, target);
|
|
1768
|
+
collectOperands(node.right, operator, target);
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
target.push(node);
|
|
1772
|
+
}
|
|
1773
|
+
var BINARY_FUNCTION_OPERATOR_MAP = {
|
|
1774
|
+
">": "greaterthan",
|
|
1775
|
+
"<": "lessthan",
|
|
1776
|
+
"==": "equal",
|
|
1777
|
+
"!=": "notequal",
|
|
1778
|
+
"&&": "and",
|
|
1779
|
+
"||": "or"
|
|
1780
|
+
};
|
|
1781
|
+
function materializeExpression(node, context, aliasNodes, issues) {
|
|
1782
|
+
switch (node.type) {
|
|
1783
|
+
case "Literal": {
|
|
1784
|
+
return getConstantNodeId(context, node.value);
|
|
1785
|
+
}
|
|
1786
|
+
case "Reference": {
|
|
1787
|
+
const mapped = aliasNodes.get(node.name);
|
|
1788
|
+
if (!mapped) {
|
|
1789
|
+
issues.push(`Unknown control "${node.name}".`);
|
|
1790
|
+
return getConstantNodeId(context, 0);
|
|
1791
|
+
}
|
|
1792
|
+
return mapped;
|
|
1793
|
+
}
|
|
1794
|
+
case "Unary": {
|
|
1795
|
+
const operandId = materializeExpression(
|
|
1796
|
+
node.operand,
|
|
1797
|
+
context,
|
|
1798
|
+
aliasNodes,
|
|
1799
|
+
issues
|
|
1800
|
+
);
|
|
1801
|
+
switch (node.operator) {
|
|
1802
|
+
case "+":
|
|
1803
|
+
return operandId;
|
|
1804
|
+
case "-": {
|
|
1805
|
+
const negativeOne = getConstantNodeId(context, -1);
|
|
1806
|
+
return createVariadicOperationNode(context, "multiply", [
|
|
1807
|
+
negativeOne,
|
|
1808
|
+
operandId
|
|
1809
|
+
]);
|
|
1810
|
+
}
|
|
1811
|
+
case "!": {
|
|
1812
|
+
const definition = SCALAR_FUNCTIONS.get("not");
|
|
1813
|
+
if (!definition) {
|
|
1814
|
+
issues.push('Function "not" is not available in metadata.');
|
|
1815
|
+
return getConstantNodeId(context, 0);
|
|
1816
|
+
}
|
|
1817
|
+
return emitScalarFunctionNode(definition, [operandId], context);
|
|
1818
|
+
}
|
|
1819
|
+
default:
|
|
1820
|
+
issues.push("Unsupported unary operator.");
|
|
1821
|
+
return operandId;
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
case "Binary": {
|
|
1825
|
+
const operator = node.operator;
|
|
1826
|
+
if (operator === "+") {
|
|
1827
|
+
const children = [];
|
|
1828
|
+
collectOperands(node, "+", children);
|
|
1829
|
+
const operandIds = children.map(
|
|
1830
|
+
(child) => materializeExpression(child, context, aliasNodes, issues)
|
|
1831
|
+
);
|
|
1832
|
+
return createVariadicOperationNode(context, "add", operandIds);
|
|
1833
|
+
}
|
|
1834
|
+
if (operator === "*") {
|
|
1835
|
+
const children = [];
|
|
1836
|
+
collectOperands(node, "*", children);
|
|
1837
|
+
const operandIds = children.map(
|
|
1838
|
+
(child) => materializeExpression(child, context, aliasNodes, issues)
|
|
1839
|
+
);
|
|
1840
|
+
return createVariadicOperationNode(context, "multiply", operandIds);
|
|
1841
|
+
}
|
|
1842
|
+
const leftId = materializeExpression(
|
|
1843
|
+
node.left,
|
|
1844
|
+
context,
|
|
1845
|
+
aliasNodes,
|
|
1846
|
+
issues
|
|
1847
|
+
);
|
|
1848
|
+
const rightId = materializeExpression(
|
|
1849
|
+
node.right,
|
|
1850
|
+
context,
|
|
1851
|
+
aliasNodes,
|
|
1852
|
+
issues
|
|
1853
|
+
);
|
|
1854
|
+
if (operator === "-") {
|
|
1855
|
+
return createBinaryOperationNode(context, "subtract", leftId, rightId);
|
|
1856
|
+
}
|
|
1857
|
+
if (operator === "/") {
|
|
1858
|
+
return createBinaryOperationNode(context, "divide", leftId, rightId);
|
|
1859
|
+
}
|
|
1860
|
+
const mappedFunction = BINARY_FUNCTION_OPERATOR_MAP[operator];
|
|
1861
|
+
if (mappedFunction) {
|
|
1862
|
+
const definition = SCALAR_FUNCTIONS.get(mappedFunction);
|
|
1863
|
+
if (!definition) {
|
|
1864
|
+
issues.push(`Function "${mappedFunction}" is not available.`);
|
|
1865
|
+
return getConstantNodeId(context, 0);
|
|
1866
|
+
}
|
|
1867
|
+
return emitScalarFunctionNode(definition, [leftId, rightId], context);
|
|
1868
|
+
}
|
|
1869
|
+
issues.push(`Unsupported operator "${operator}".`);
|
|
1870
|
+
return getConstantNodeId(context, 0);
|
|
1871
|
+
}
|
|
1872
|
+
case "Function": {
|
|
1873
|
+
const name = node.name;
|
|
1874
|
+
const normalized = name.toLowerCase();
|
|
1875
|
+
const definition = SCALAR_FUNCTIONS.get(normalized);
|
|
1876
|
+
if (!definition) {
|
|
1877
|
+
issues.push(`Unknown function "${name}".`);
|
|
1878
|
+
return getConstantNodeId(context, 0);
|
|
1879
|
+
}
|
|
1880
|
+
const operands = node.args.map(
|
|
1881
|
+
(arg) => materializeExpression(arg, context, aliasNodes, issues)
|
|
1882
|
+
);
|
|
1883
|
+
if (operands.length < definition.minArgs) {
|
|
1884
|
+
issues.push(
|
|
1885
|
+
`Function "${name}" expects at least ${definition.minArgs} arguments, received ${operands.length}.`
|
|
1886
|
+
);
|
|
1887
|
+
return getConstantNodeId(context, 0);
|
|
1888
|
+
}
|
|
1889
|
+
if (definition.maxArgs !== null && operands.length > definition.maxArgs) {
|
|
1890
|
+
issues.push(
|
|
1891
|
+
`Function "${name}" expects at most ${definition.maxArgs} arguments, received ${operands.length}.`
|
|
1892
|
+
);
|
|
1893
|
+
return getConstantNodeId(context, 0);
|
|
1894
|
+
}
|
|
1895
|
+
return emitScalarFunctionNode(definition, operands, context);
|
|
1896
|
+
}
|
|
1897
|
+
default: {
|
|
1898
|
+
issues.push("Unsupported expression node.");
|
|
1899
|
+
return getConstantNodeId(context, 0);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
function buildRigGraphSpec({
|
|
1904
|
+
faceId,
|
|
1905
|
+
animatables,
|
|
1906
|
+
components,
|
|
1907
|
+
bindings,
|
|
1908
|
+
inputsById,
|
|
1909
|
+
inputBindings
|
|
1910
|
+
}) {
|
|
1911
|
+
const nodes = [];
|
|
1912
|
+
const edges = [];
|
|
1913
|
+
const inputNodes = /* @__PURE__ */ new Map();
|
|
1914
|
+
const buildingDerived = /* @__PURE__ */ new Set();
|
|
1915
|
+
const computedInputs = /* @__PURE__ */ new Set();
|
|
1916
|
+
const summaryBindings = [];
|
|
1917
|
+
const bindingIssues = /* @__PURE__ */ new Map();
|
|
1918
|
+
const animatableEntries = /* @__PURE__ */ new Map();
|
|
1919
|
+
const outputs = /* @__PURE__ */ new Set();
|
|
1920
|
+
const ensureInputNode = (inputId) => {
|
|
1921
|
+
const existing = inputNodes.get(inputId);
|
|
1922
|
+
if (existing) {
|
|
1923
|
+
return existing;
|
|
1924
|
+
}
|
|
1925
|
+
const input = inputsById.get(inputId);
|
|
1926
|
+
if (!input) {
|
|
1927
|
+
return null;
|
|
1928
|
+
}
|
|
1929
|
+
const defaultValue = Number.isFinite(input.defaultValue) ? input.defaultValue : 0;
|
|
1930
|
+
const inputBindingRaw = inputBindings[inputId];
|
|
1931
|
+
if (inputBindingRaw) {
|
|
1932
|
+
if (buildingDerived.has(inputId)) {
|
|
1933
|
+
const issueSet = bindingIssues.get(inputId) ?? /* @__PURE__ */ new Set();
|
|
1934
|
+
issueSet.add("Derived input cycle detected.");
|
|
1935
|
+
bindingIssues.set(inputId, issueSet);
|
|
1936
|
+
return null;
|
|
1937
|
+
}
|
|
1938
|
+
buildingDerived.add(inputId);
|
|
1939
|
+
try {
|
|
1940
|
+
const target = bindingTargetFromInput(input);
|
|
1941
|
+
const binding = ensureBindingStructure(inputBindingRaw, target);
|
|
1942
|
+
const requiresSelf = binding.inputId === SELF_BINDING_ID2 || binding.slots.some((slot) => slot.inputId === SELF_BINDING_ID2);
|
|
1943
|
+
let selfNodeId;
|
|
1944
|
+
if (requiresSelf) {
|
|
1945
|
+
const sliderNodeId = `input_raw_${sanitizeNodeId(inputId)}`;
|
|
1946
|
+
nodes.push({
|
|
1947
|
+
id: sliderNodeId,
|
|
1948
|
+
type: "input",
|
|
1949
|
+
params: {
|
|
1950
|
+
path: buildRigInputPath(faceId, input.path),
|
|
1951
|
+
value: { float: defaultValue }
|
|
1952
|
+
}
|
|
1953
|
+
});
|
|
1954
|
+
selfNodeId = sliderNodeId;
|
|
1955
|
+
}
|
|
1956
|
+
const { valueNodeId, hasActiveSlot } = evaluateBinding({
|
|
1957
|
+
binding,
|
|
1958
|
+
target,
|
|
1959
|
+
targetId: inputId,
|
|
1960
|
+
animatableId: inputId,
|
|
1961
|
+
component: void 0,
|
|
1962
|
+
safeId: sanitizeNodeId(inputId),
|
|
1963
|
+
context: {
|
|
1964
|
+
nodes,
|
|
1965
|
+
edges,
|
|
1966
|
+
ensureInputNode,
|
|
1967
|
+
bindingIssues,
|
|
1968
|
+
summaryBindings
|
|
1969
|
+
},
|
|
1970
|
+
selfNodeId
|
|
1971
|
+
});
|
|
1972
|
+
if (!valueNodeId || !hasActiveSlot) {
|
|
1973
|
+
const constNodeId = `derived_default_${sanitizeNodeId(inputId)}`;
|
|
1974
|
+
nodes.push({
|
|
1975
|
+
id: constNodeId,
|
|
1976
|
+
type: "constant",
|
|
1977
|
+
params: {
|
|
1978
|
+
value: input.defaultValue
|
|
1979
|
+
}
|
|
1980
|
+
});
|
|
1981
|
+
const record3 = { nodeId: constNodeId, input };
|
|
1982
|
+
inputNodes.set(inputId, record3);
|
|
1983
|
+
return record3;
|
|
1984
|
+
}
|
|
1985
|
+
computedInputs.add(inputId);
|
|
1986
|
+
const record2 = { nodeId: valueNodeId, input };
|
|
1987
|
+
inputNodes.set(inputId, record2);
|
|
1988
|
+
return record2;
|
|
1989
|
+
} finally {
|
|
1990
|
+
buildingDerived.delete(inputId);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
const nodeId = `input_${sanitizeNodeId(inputId)}`;
|
|
1994
|
+
nodes.push({
|
|
1995
|
+
id: nodeId,
|
|
1996
|
+
type: "input",
|
|
1997
|
+
params: {
|
|
1998
|
+
path: buildRigInputPath(faceId, input.path),
|
|
1999
|
+
value: { float: defaultValue }
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
const record = { nodeId, input };
|
|
2003
|
+
inputNodes.set(inputId, record);
|
|
2004
|
+
return record;
|
|
2005
|
+
};
|
|
2006
|
+
const ensureAnimatableEntry = (animatableId) => {
|
|
2007
|
+
const existing = animatableEntries.get(animatableId);
|
|
2008
|
+
if (existing) {
|
|
2009
|
+
return existing;
|
|
2010
|
+
}
|
|
2011
|
+
const animatable = animatables[animatableId];
|
|
2012
|
+
if (!animatable) {
|
|
2013
|
+
return null;
|
|
2014
|
+
}
|
|
2015
|
+
const entry = {
|
|
2016
|
+
animatable,
|
|
2017
|
+
values: /* @__PURE__ */ new Map(),
|
|
2018
|
+
defaults: /* @__PURE__ */ new Map(),
|
|
2019
|
+
isDriven: false
|
|
2020
|
+
};
|
|
2021
|
+
animatableEntries.set(animatableId, entry);
|
|
2022
|
+
return entry;
|
|
2023
|
+
};
|
|
2024
|
+
components.forEach((component) => {
|
|
2025
|
+
const bindingRaw = bindings[component.id];
|
|
2026
|
+
const target = bindingTargetFromComponent(component);
|
|
2027
|
+
const binding = bindingRaw ? ensureBindingStructure(bindingRaw, target) : null;
|
|
2028
|
+
const entry = ensureAnimatableEntry(component.animatableId);
|
|
2029
|
+
if (!entry) {
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
const key = component.component ?? "scalar";
|
|
2033
|
+
let valueNodeId = null;
|
|
2034
|
+
let hasActiveSlot = false;
|
|
2035
|
+
if (binding) {
|
|
2036
|
+
const { valueNodeId: producedNodeId, hasActiveSlot: active } = evaluateBinding({
|
|
2037
|
+
binding,
|
|
2038
|
+
target,
|
|
2039
|
+
targetId: component.id,
|
|
2040
|
+
animatableId: component.animatableId,
|
|
2041
|
+
component: component.component,
|
|
2042
|
+
safeId: component.safeId,
|
|
2043
|
+
context: {
|
|
2044
|
+
nodes,
|
|
2045
|
+
edges,
|
|
2046
|
+
ensureInputNode,
|
|
2047
|
+
bindingIssues,
|
|
2048
|
+
summaryBindings
|
|
2049
|
+
}
|
|
2050
|
+
});
|
|
2051
|
+
valueNodeId = producedNodeId;
|
|
2052
|
+
hasActiveSlot = active;
|
|
2053
|
+
if (active) {
|
|
2054
|
+
entry.isDriven = true;
|
|
2055
|
+
}
|
|
2056
|
+
} else {
|
|
2057
|
+
summaryBindings.push({
|
|
2058
|
+
targetId: component.id,
|
|
2059
|
+
animatableId: component.animatableId,
|
|
2060
|
+
component: component.component,
|
|
2061
|
+
slotId: PRIMARY_SLOT_ID,
|
|
2062
|
+
slotAlias: PRIMARY_SLOT_ALIAS,
|
|
2063
|
+
inputId: null,
|
|
2064
|
+
remap: createDefaultRemap(target),
|
|
2065
|
+
expression: PRIMARY_SLOT_ALIAS,
|
|
2066
|
+
valueType: target.valueType === "vector" ? "vector" : "scalar",
|
|
2067
|
+
issues: ["Binding not found."]
|
|
2068
|
+
});
|
|
2069
|
+
const fallbackIssues = bindingIssues.get(component.id) ?? /* @__PURE__ */ new Set();
|
|
2070
|
+
fallbackIssues.add("Binding not found.");
|
|
2071
|
+
bindingIssues.set(component.id, fallbackIssues);
|
|
2072
|
+
}
|
|
2073
|
+
if (!valueNodeId || !hasActiveSlot) {
|
|
2074
|
+
entry.defaults.set(key, component.defaultValue);
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
const operatorList = binding ? binding.operators ?? [] : [];
|
|
2078
|
+
const finalNodeId = applyBindingOperators(
|
|
2079
|
+
operatorList,
|
|
2080
|
+
valueNodeId,
|
|
2081
|
+
component.safeId,
|
|
2082
|
+
nodes,
|
|
2083
|
+
edges
|
|
2084
|
+
);
|
|
2085
|
+
entry.values.set(key, finalNodeId);
|
|
2086
|
+
});
|
|
2087
|
+
inputsById.forEach((_input, inputId) => {
|
|
2088
|
+
ensureInputNode(inputId);
|
|
2089
|
+
});
|
|
2090
|
+
animatableEntries.forEach((entry, animatableId) => {
|
|
2091
|
+
if (!entry.isDriven) {
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
outputs.add(animatableId);
|
|
2095
|
+
const safeId = sanitizeNodeId(animatableId);
|
|
2096
|
+
const order = getComponentOrder(entry.animatable);
|
|
2097
|
+
if (!order) {
|
|
2098
|
+
const valueNodeId = entry.values.get("scalar");
|
|
2099
|
+
if (!valueNodeId) {
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
const outputNodeId2 = `out_${safeId}`;
|
|
2103
|
+
nodes.push({
|
|
2104
|
+
id: outputNodeId2,
|
|
2105
|
+
type: "output",
|
|
2106
|
+
params: {
|
|
2107
|
+
path: animatableId
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
edges.push({
|
|
2111
|
+
from: { node_id: valueNodeId },
|
|
2112
|
+
to: { node_id: outputNodeId2, input: "in" }
|
|
2113
|
+
});
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
const joinNodeId = `join_${safeId}`;
|
|
2117
|
+
nodes.push({
|
|
2118
|
+
id: joinNodeId,
|
|
2119
|
+
type: "join"
|
|
2120
|
+
});
|
|
2121
|
+
order.forEach((componentKey, index) => {
|
|
2122
|
+
let sourceId = entry.values.get(componentKey);
|
|
2123
|
+
if (!sourceId) {
|
|
2124
|
+
const componentDefault = entry.defaults.get(componentKey) ?? extractComponentDefault(
|
|
2125
|
+
buildAnimatableValue(entry.animatable, void 0),
|
|
2126
|
+
componentKey
|
|
2127
|
+
);
|
|
2128
|
+
const constNodeId = `const_${safeId}_${componentKey}`;
|
|
2129
|
+
nodes.push({
|
|
2130
|
+
id: constNodeId,
|
|
2131
|
+
type: "constant",
|
|
2132
|
+
params: {
|
|
2133
|
+
value: componentDefault
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
sourceId = constNodeId;
|
|
2137
|
+
}
|
|
2138
|
+
edges.push({
|
|
2139
|
+
from: { node_id: sourceId },
|
|
2140
|
+
to: { node_id: joinNodeId, input: `operand_${index + 1}` }
|
|
2141
|
+
});
|
|
2142
|
+
});
|
|
2143
|
+
const outputNodeId = `out_${safeId}`;
|
|
2144
|
+
nodes.push({
|
|
2145
|
+
id: outputNodeId,
|
|
2146
|
+
type: "output",
|
|
2147
|
+
params: {
|
|
2148
|
+
path: animatableId
|
|
2149
|
+
}
|
|
2150
|
+
});
|
|
2151
|
+
edges.push({
|
|
2152
|
+
from: { node_id: joinNodeId },
|
|
2153
|
+
to: { node_id: outputNodeId, input: "in" }
|
|
2154
|
+
});
|
|
2155
|
+
});
|
|
2156
|
+
const nodeById = /* @__PURE__ */ new Map();
|
|
2157
|
+
nodes.forEach((node) => {
|
|
2158
|
+
nodeById.set(node.id, node);
|
|
2159
|
+
});
|
|
2160
|
+
const constantUsage = /* @__PURE__ */ new Map();
|
|
2161
|
+
edges.forEach((edge) => {
|
|
2162
|
+
const source = nodeById.get(edge.from.node_id);
|
|
2163
|
+
if (source?.type === "constant") {
|
|
2164
|
+
constantUsage.set(source.id, (constantUsage.get(source.id) ?? 0) + 1);
|
|
2165
|
+
}
|
|
2166
|
+
});
|
|
2167
|
+
const updatedEdges = [];
|
|
2168
|
+
const constantsToRemove = /* @__PURE__ */ new Set();
|
|
2169
|
+
edges.forEach((edge) => {
|
|
2170
|
+
const source = nodeById.get(edge.from.node_id);
|
|
2171
|
+
if (source?.type === "constant" && constantUsage.get(source.id) === 1 && source.params && Object.prototype.hasOwnProperty.call(source.params, "value")) {
|
|
2172
|
+
const target = nodeById.get(edge.to.node_id);
|
|
2173
|
+
if (target) {
|
|
2174
|
+
const value = source.params.value;
|
|
2175
|
+
if (value !== void 0) {
|
|
2176
|
+
target.input_defaults = {
|
|
2177
|
+
...target.input_defaults ?? {},
|
|
2178
|
+
[edge.to.input]: value
|
|
2179
|
+
};
|
|
2180
|
+
nodeById.set(target.id, target);
|
|
2181
|
+
constantsToRemove.add(source.id);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
updatedEdges.push(edge);
|
|
2187
|
+
});
|
|
2188
|
+
const filteredNodes = nodes.filter((node) => !constantsToRemove.has(node.id)).map((node) => nodeById.get(node.id) ?? node);
|
|
2189
|
+
const remapDefaultIssues = validateRemapDefaults(filteredNodes);
|
|
2190
|
+
const dynamicOutputs = Array.from(outputs);
|
|
2191
|
+
const computedInputList = Array.from(computedInputs);
|
|
2192
|
+
const filteredSummaryBindings = summaryBindings.filter(
|
|
2193
|
+
(binding) => outputs.has(binding.animatableId) || computedInputs.has(binding.animatableId)
|
|
2194
|
+
);
|
|
2195
|
+
const spec = {
|
|
2196
|
+
nodes: filteredNodes,
|
|
2197
|
+
edges: updatedEdges.length ? updatedEdges : void 0
|
|
2198
|
+
};
|
|
2199
|
+
const baseSpec = spec;
|
|
2200
|
+
const specWithMetadata = {
|
|
2201
|
+
...baseSpec,
|
|
2202
|
+
metadata: {
|
|
2203
|
+
...baseSpec.metadata,
|
|
2204
|
+
vizij: {
|
|
2205
|
+
faceId,
|
|
2206
|
+
inputs: Array.from(inputsById.values()).map((input) => ({
|
|
2207
|
+
id: input.id,
|
|
2208
|
+
path: input.path,
|
|
2209
|
+
sourceId: input.sourceId,
|
|
2210
|
+
label: input.label,
|
|
2211
|
+
group: input.group,
|
|
2212
|
+
defaultValue: input.defaultValue,
|
|
2213
|
+
range: {
|
|
2214
|
+
min: input.range.min,
|
|
2215
|
+
max: input.range.max
|
|
2216
|
+
}
|
|
2217
|
+
})),
|
|
2218
|
+
bindings: filteredSummaryBindings.map((binding) => ({
|
|
2219
|
+
...binding,
|
|
2220
|
+
remap: { ...binding.remap },
|
|
2221
|
+
expression: binding.expression,
|
|
2222
|
+
valueType: binding.valueType,
|
|
2223
|
+
issues: binding.issues ? [...binding.issues] : void 0
|
|
2224
|
+
}))
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
};
|
|
2228
|
+
const issuesByTarget = {};
|
|
2229
|
+
bindingIssues.forEach((issues, targetId) => {
|
|
2230
|
+
if (issues.size === 0) {
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
issuesByTarget[targetId] = Array.from(issues);
|
|
2234
|
+
});
|
|
2235
|
+
const fatalIssues = /* @__PURE__ */ new Set();
|
|
2236
|
+
Object.values(issuesByTarget).forEach((issues) => {
|
|
2237
|
+
issues.forEach((issue) => fatalIssues.add(issue));
|
|
2238
|
+
});
|
|
2239
|
+
remapDefaultIssues.forEach((issue) => fatalIssues.add(issue));
|
|
2240
|
+
return {
|
|
2241
|
+
spec: specWithMetadata,
|
|
2242
|
+
summary: {
|
|
2243
|
+
faceId,
|
|
2244
|
+
inputs: Array.from(inputNodes.values()).map(
|
|
2245
|
+
({ input }) => buildRigInputPath(faceId, input.path)
|
|
2246
|
+
),
|
|
2247
|
+
outputs: [...dynamicOutputs, ...computedInputList],
|
|
2248
|
+
bindings: filteredSummaryBindings
|
|
2249
|
+
},
|
|
2250
|
+
issues: {
|
|
2251
|
+
byTarget: issuesByTarget,
|
|
2252
|
+
fatal: Array.from(fatalIssues)
|
|
2253
|
+
}
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
function validateRemapDefaults(nodes) {
|
|
2257
|
+
const issues = [];
|
|
2258
|
+
nodes.forEach((node) => {
|
|
2259
|
+
if (node.type !== "centered_remap") {
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
const defaults = node.input_defaults ?? {};
|
|
2263
|
+
[
|
|
2264
|
+
"in_low",
|
|
2265
|
+
"in_anchor",
|
|
2266
|
+
"in_high",
|
|
2267
|
+
"out_low",
|
|
2268
|
+
"out_anchor",
|
|
2269
|
+
"out_high"
|
|
2270
|
+
].forEach((key) => {
|
|
2271
|
+
const value = defaults[key];
|
|
2272
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2273
|
+
issues.push(`Remap node ${node.id} missing ${key} default.`);
|
|
2274
|
+
}
|
|
2275
|
+
});
|
|
2276
|
+
});
|
|
2277
|
+
return issues;
|
|
2278
|
+
}
|
|
2279
|
+
export {
|
|
2280
|
+
PRIMARY_SLOT_ALIAS,
|
|
2281
|
+
PRIMARY_SLOT_ID,
|
|
2282
|
+
addBindingSlot,
|
|
2283
|
+
bindingFromDefinition,
|
|
2284
|
+
bindingOperatorDefinitions,
|
|
2285
|
+
bindingOperatorTypes,
|
|
2286
|
+
bindingTargetFromComponent,
|
|
2287
|
+
bindingTargetFromInput,
|
|
2288
|
+
bindingToDefinition,
|
|
2289
|
+
buildRigGraphSpec,
|
|
2290
|
+
collectExpressionReferences,
|
|
2291
|
+
createDefaultBinding,
|
|
2292
|
+
createDefaultBindings,
|
|
2293
|
+
createDefaultInputValues,
|
|
2294
|
+
createDefaultOperatorSettings,
|
|
2295
|
+
createDefaultParentBinding,
|
|
2296
|
+
createDefaultRemap,
|
|
2297
|
+
ensureBindingStructure,
|
|
2298
|
+
ensureOperatorParams,
|
|
2299
|
+
getBindingOperatorDefinition,
|
|
2300
|
+
getPrimaryBindingSlot,
|
|
2301
|
+
mapExpression,
|
|
2302
|
+
parseControlExpression,
|
|
2303
|
+
reconcileBindings,
|
|
2304
|
+
remapValue,
|
|
2305
|
+
removeBindingSlot,
|
|
2306
|
+
setBindingOperatorEnabled,
|
|
2307
|
+
updateBindingExpression,
|
|
2308
|
+
updateBindingOperatorParam,
|
|
2309
|
+
updateBindingSlotAlias,
|
|
2310
|
+
updateBindingSlotRemap,
|
|
2311
|
+
updateBindingWithInput
|
|
2312
|
+
};
|