@pinepaper.studio/mcp-server 1.6.26 → 1.6.27
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/README.md +71 -1
- package/SECURITY.md +2 -2
- package/dist/agent/types.d.ts +3 -3
- package/dist/browser/puppeteer-controller.d.ts +11 -0
- package/dist/browser/puppeteer-controller.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.js +38 -2
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/build-info.json +2 -2
- package/dist/ontology/hints.d.ts +3 -1
- package/dist/ontology/hints.d.ts.map +1 -1
- package/dist/ontology/hints.js +4 -2
- package/dist/ontology/hints.js.map +1 -1
- package/dist/ontology/upstream-vocabulary.d.ts +1 -1
- package/dist/ontology/upstream-vocabulary.js +1 -1
- package/dist/ontology/vocabulary.d.ts.map +1 -1
- package/dist/ontology/vocabulary.js +2 -0
- package/dist/ontology/vocabulary.js.map +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +23 -10
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-lists.d.ts +11 -1
- package/dist/tools/engine-lists.d.ts.map +1 -1
- package/dist/tools/engine-lists.js +109 -1
- package/dist/tools/engine-lists.js.map +1 -1
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +1 -0
- package/dist/tools/engine-methods.js.map +1 -1
- package/dist/tools/engine-surface.d.ts +1 -1
- package/dist/tools/engine-surface.d.ts.map +1 -1
- package/dist/tools/engine-surface.js +7 -4
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/export-file-receipt.d.ts +43 -0
- package/dist/tools/export-file-receipt.d.ts.map +1 -1
- package/dist/tools/export-file-receipt.js +61 -6
- package/dist/tools/export-file-receipt.js.map +1 -1
- package/dist/tools/expression-grammar.d.ts +10 -0
- package/dist/tools/expression-grammar.d.ts.map +1 -0
- package/dist/tools/expression-grammar.js +33 -0
- package/dist/tools/expression-grammar.js.map +1 -0
- package/dist/tools/handlers/export.d.ts.map +1 -1
- package/dist/tools/handlers/export.js +54 -4
- package/dist/tools/handlers/export.js.map +1 -1
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +61 -2
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/lane.d.ts +4 -3
- package/dist/tools/lane.d.ts.map +1 -1
- package/dist/tools/lane.js +31 -5
- package/dist/tools/lane.js.map +1 -1
- package/dist/tools/scene-cache-export.d.ts +52 -0
- package/dist/tools/scene-cache-export.d.ts.map +1 -0
- package/dist/tools/scene-cache-export.js +227 -0
- package/dist/tools/scene-cache-export.js.map +1 -0
- package/dist/tools/shape-params.d.ts +2 -2
- package/dist/tools/shape-params.js +2 -2
- package/dist/tools/stick-vocabulary.d.ts +1 -1
- package/dist/tools/stick-vocabulary.js +1 -1
- package/dist/types/code-generator.d.ts +1 -0
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +437 -249
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +211 -199
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +7 -1
- package/dist/types/schemas.js.map +1 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -7,11 +7,18 @@
|
|
|
7
7
|
import { compileChoreography } from '../tools/choreograph.js';
|
|
8
8
|
import { compileBatchTimelines, emitTimelineOpBody } from '../tools/timeline.js';
|
|
9
9
|
import { normalizeSvgSize } from '../utils/svg-size.js';
|
|
10
|
-
import { WORLD3D_COLOR_PATHS, PATTERN_KINDS } from '../tools/engine-lists.js';
|
|
10
|
+
import { WORLD3D_COLOR_PATHS, PATTERN_KINDS, EXPRESSION_FUNCTION_ARITY, EXPRESSION_CONSTANTS, EXPRESSION_REJECTED, RELATION_PARAM_KEYS } from '../tools/engine-lists.js';
|
|
11
|
+
import { expressionGrammarText } from '../tools/expression-grammar.js';
|
|
11
12
|
import { REQUIRED_ENGINE_METHODS, OPTIONAL_ENGINE_METHODS } from '../tools/engine-methods.js';
|
|
12
13
|
import { NORMALIZE_PARAM_READS, MODIFY_CHANGE_READS } from '../tools/shape-params.js';
|
|
13
14
|
import { CREATE_KNOWN_KEYS } from '../tools/create-known-keys.js';
|
|
14
15
|
import { jsLiteral } from '../utils/js-literal.js';
|
|
16
|
+
/** A value spliced into a generated `//` comment, on one line: a line break in caller text would end the comment and run the rest as code. */
|
|
17
|
+
/** How many relations pinepaper_lint_scene lists back; past it the result says how many there are. */
|
|
18
|
+
const RELATIONS_READBACK_CAP = 200;
|
|
19
|
+
function oneLine(v) {
|
|
20
|
+
return String(v).replace(/[\r\n\u2028\u2029]+/g, ' ');
|
|
21
|
+
}
|
|
15
22
|
import { generateP5DrawCode } from '../tools/p5-compat/p5-helpers.js';
|
|
16
23
|
import { expandBatchMacros } from '../tools/batch-macros.js';
|
|
17
24
|
import { CreateItemInputSchema, ScreenSpaceSchema, StepTimingSchema, CounterSchema, ImportMotionCaptureInputSchema, ModifyItemInputSchema, AddRelationInputSchema, AnimateItemInputSchema, KeyframeAnimateInputSchema, ExecuteGeneratorInputSchema, ApplyEffectInputSchema, SetBackgroundColorInputSchema, SetCanvasSizeInputSchema, CreateGlossySphereInputSchema, CreateDiagonalStripesInputSchema, BatchCreateInputSchema, BatchModifyInputSchema, CreateGridInputSchema, CreateSceneInputSchema,
|
|
@@ -78,7 +85,7 @@ function normalizeGradientSpec(v, key) {
|
|
|
78
85
|
*/
|
|
79
86
|
function generateGradientCode(gradient, itemVarName, position, propertyName = 'fillColor') {
|
|
80
87
|
const { type, stops, origin, destination } = gradient;
|
|
81
|
-
const stopsCode = stops.map(s => `[
|
|
88
|
+
const stopsCode = stops.map(s => `[${jsLiteral(s.color)}, ${s.offset}]`).join(', ');
|
|
82
89
|
if (type === 'radial') {
|
|
83
90
|
// A RADIAL CORE BELONGS ON THE ITEM, NOT ON THE NUMBER ASKED FOR (17
|
|
84
91
|
// RADIALOFFSET). The origin was the REQUESTED position and the radius a
|
|
@@ -126,7 +133,7 @@ ${itemVarName}.${propertyName} = {
|
|
|
126
133
|
function generateShadowCode(itemVarName, shadowColor, shadowBlur, shadowOffset) {
|
|
127
134
|
const lines = [];
|
|
128
135
|
if (shadowColor) {
|
|
129
|
-
lines.push(`${itemVarName}.shadowColor =
|
|
136
|
+
lines.push(`${itemVarName}.shadowColor = ${jsLiteral(shadowColor)};`);
|
|
130
137
|
}
|
|
131
138
|
if (shadowBlur !== undefined) {
|
|
132
139
|
lines.push(`${itemVarName}.shadowBlur = ${shadowBlur};`);
|
|
@@ -140,7 +147,7 @@ function generateShadowCode(itemVarName, shadowColor, shadowBlur, shadowOffset)
|
|
|
140
147
|
* Generate blend mode code for an item
|
|
141
148
|
*/
|
|
142
149
|
function generateBlendModeCode(itemVarName, blendMode) {
|
|
143
|
-
return `${itemVarName}.blendMode =
|
|
150
|
+
return `${itemVarName}.blendMode = ${jsLiteral(blendMode)};`;
|
|
144
151
|
}
|
|
145
152
|
// =============================================================================
|
|
146
153
|
// CODE TEMPLATES
|
|
@@ -155,7 +162,7 @@ function generateCreateItemCode(itemType, position, properties, dataFlags, posit
|
|
|
155
162
|
// break those later calls: refused here, by name, before anything is made.
|
|
156
163
|
if (typeof properties.id === 'string' && UNSAFE_ID_CHARS.test(properties.id)) {
|
|
157
164
|
return `
|
|
158
|
-
// Create ${itemType} — refused: an id later calls could not address
|
|
165
|
+
// Create ${oneLine(itemType)} — refused: an id later calls could not address
|
|
159
166
|
({ success: false, error: ${jsLiteral(`properties.id ${JSON.stringify(properties.id)} has a quote, backslash or line break, so later calls could not address the item. Nothing was made. Use letters, digits, spaces, - and _ (e.g. "bobs-hat").`)} });`.trim();
|
|
160
167
|
}
|
|
161
168
|
// Colour objects are gradient specs: normalised, or refused by name.
|
|
@@ -165,7 +172,7 @@ function generateCreateItemCode(itemType, position, properties, dataFlags, posit
|
|
|
165
172
|
const g = normalizeGradientSpec(v, key);
|
|
166
173
|
if ('error' in g) {
|
|
167
174
|
return `
|
|
168
|
-
// Create ${itemType} — refused: an unreadable gradient would draw nothing
|
|
175
|
+
// Create ${oneLine(itemType)} — refused: an unreadable gradient would draw nothing
|
|
169
176
|
({ success: false, error: ${JSON.stringify(g.error)} });`.trim();
|
|
170
177
|
}
|
|
171
178
|
properties = { ...properties, [key]: g.gradient };
|
|
@@ -274,7 +281,7 @@ function generateCreateItemCode(itemType, position, properties, dataFlags, posit
|
|
|
274
281
|
if (params.opacity != null)
|
|
275
282
|
plate.opacity = params.opacity;
|
|
276
283
|
return `
|
|
277
|
-
// ${itemType} — a render-time surface: a plate tagged with what to draw over it.
|
|
284
|
+
// ${oneLine(itemType)} — a render-time surface: a plate tagged with what to draw over it.
|
|
278
285
|
const item = app.create('rectangle', ${JSON.stringify(plate, null, 2)});
|
|
279
286
|
if (item && item.data) { item.data.renderAs = ${JSON.stringify(itemType)}; item.data.renderParams = ${JSON.stringify(params)}; }
|
|
280
287
|
// The snippet's value is its last statement's, which was the assignment above:
|
|
@@ -284,7 +291,7 @@ if (item && item.data) { item.data.renderAs = ${JSON.stringify(itemType)}; item.
|
|
|
284
291
|
// and its value is lost.
|
|
285
292
|
((item && item.data)
|
|
286
293
|
? { itemId: item.data.registryId, type: ${JSON.stringify(itemType)}, position: { x: ${position.x}, y: ${position.y} } }
|
|
287
|
-
: { success: false, error: 'the plate for this ${itemType} was not created.' });`;
|
|
294
|
+
: { success: false, error: 'the plate for this ' + ${jsLiteral(itemType)} + ' was not created.' });`;
|
|
288
295
|
}
|
|
289
296
|
// AN UNKNOWN ANCHOR IS INVISIBLE OVER MCP.
|
|
290
297
|
//
|
|
@@ -299,7 +306,7 @@ if (item && item.data) { item.data.renderAs = ${JSON.stringify(itemType)}; item.
|
|
|
299
306
|
const normalized = String(anchorGiven).toLowerCase().replace(/[\s_]+/g, '-');
|
|
300
307
|
if (!ANCHORS.includes(normalized)) {
|
|
301
308
|
return `
|
|
302
|
-
// Create ${itemType} — refused, rather than silently centred
|
|
309
|
+
// Create ${oneLine(itemType)} — refused, rather than silently centred
|
|
303
310
|
({ success: false, error: ${JSON.stringify(`anchor ${JSON.stringify(String(anchorGiven))} is not one of ${ANCHORS.slice(0, 5).join(', ')}. `
|
|
304
311
|
+ 'The engine would leave x/y as the CENTRE and say so only in a console this tool cannot read, '
|
|
305
312
|
+ 'so the item would land half its own size away with nothing to explain it.')} });`.trim();
|
|
@@ -331,8 +338,8 @@ if (item && item.data) { item.data.renderAs = ${JSON.stringify(itemType)}; item.
|
|
|
331
338
|
: !CREATE_KNOWN_KEYS.has(k) || textOnly(k));
|
|
332
339
|
// Build the code
|
|
333
340
|
let code = `
|
|
334
|
-
// Create ${itemType} item
|
|
335
|
-
const item = app.create(
|
|
341
|
+
// Create ${oneLine(itemType)} item
|
|
342
|
+
const item = app.create(${jsLiteral(itemType)}, ${JSON.stringify(params, null, 2)});`;
|
|
336
343
|
// Add gradient support for colors
|
|
337
344
|
if (color && isGradient(color)) {
|
|
338
345
|
code += generateGradientCode(color, 'item', position, 'fillColor');
|
|
@@ -413,7 +420,7 @@ const item = app.create('${itemType}', ${JSON.stringify(params, null, 2)});`;
|
|
|
413
420
|
opts.countdownTarget = countdownTarget;
|
|
414
421
|
if (countdownEndText)
|
|
415
422
|
opts.countdownEndText = countdownEndText;
|
|
416
|
-
code += `\nif (app.setDynamicContent) app.setDynamicContent(item,
|
|
423
|
+
code += `\nif (app.setDynamicContent) app.setDynamicContent(item, ${jsLiteral(contentType)}, ${JSON.stringify(opts)});`;
|
|
417
424
|
}
|
|
418
425
|
const loadsFont = itemType === 'text' && typeof properties.fontFamily === 'string' && properties.fontFamily.trim() !== '';
|
|
419
426
|
// A LINE WITH `from` IS DRAWN WHERE from AND to SAY (issue #29 O8). They are
|
|
@@ -435,7 +442,7 @@ const itemId = item.data.registryId;
|
|
|
435
442
|
app.historyManager.saveState();
|
|
436
443
|
|
|
437
444
|
// Return item info
|
|
438
|
-
({ itemId, type:
|
|
445
|
+
({ itemId, type: ${jsLiteral(itemType)}${outlineOnly ? `, fill: ${JSON.stringify(OUTLINE_NOTE)}` : ''}${textFit ? ', textFit: __textFit' : ''}${textDirection ? ', direction: __direction' : ''}${textTabular ? ', tabularFigures: __tabular' : ''}${createFillOpacity ? ', fillOpacity: __fillOpacity' : ''}${textStrokePos ? ', strokePosition: __strokePos' : ''}, position: ${reportActualPosition ? `(item.position ? { x: item.position.x, y: item.position.y } : null)` : `{ x: ${position.x}, y: ${position.y} }`}${hasLifetime ? ', lifetime: __lifetime' : ''}${loadsFont ? ', ...(__font ? { font: __font } : {})' : ''}${ignored.length > 0
|
|
439
446
|
? `, ignoredProperties: ${JSON.stringify(ignored)}` : ''}${warnings.length > 0 ? `, warning: ${JSON.stringify(warnings.join(' '))}` : ''} });`;
|
|
440
447
|
// Loading a font is async, and it has to happen BEFORE create() measures the
|
|
441
448
|
// text — so only this case becomes an async IIFE; every other create stays a
|
|
@@ -1003,7 +1010,7 @@ if (entry && entry.item && app.removeDynamicContent) app.removeDynamicContent(en
|
|
|
1003
1010
|
opts.countdownEndText = countdownEndText;
|
|
1004
1011
|
code += `
|
|
1005
1012
|
const entry = app.itemRegistry.get(${ID});
|
|
1006
|
-
if (entry && entry.item && app.setDynamicContent) app.setDynamicContent(entry.item,
|
|
1013
|
+
if (entry && entry.item && app.setDynamicContent) app.setDynamicContent(entry.item, ${jsLiteral(contentType)}, ${jsLiteral(opts)});`;
|
|
1007
1014
|
}
|
|
1008
1015
|
}
|
|
1009
1016
|
// AN AUDIO ITEM'S LEVEL IS NOT AN ITEM PROPERTY. modifyItem applies changes
|
|
@@ -1221,6 +1228,8 @@ export function relationSpecProblem(relationType, params) {
|
|
|
1221
1228
|
const p = (params && typeof params === 'object' ? params : {});
|
|
1222
1229
|
if (relationType === 'on_event_fire_if')
|
|
1223
1230
|
return fireIfProblem(p);
|
|
1231
|
+
if (relationType === 'time_expression' && typeof p.expression === 'string')
|
|
1232
|
+
return expressionProblem(p.expression);
|
|
1224
1233
|
if (typeof relationType === 'string' && VISIBILITY_REACTION.test(relationType))
|
|
1225
1234
|
return visibilityProblem(relationType, p);
|
|
1226
1235
|
if (relationType === 'on_event_add_relation' && (p.type === 'on_event_fire_if' || (typeof p.type === 'string' && VISIBILITY_REACTION.test(p.type)))) {
|
|
@@ -1230,6 +1239,110 @@ export function relationSpecProblem(relationType, params) {
|
|
|
1230
1239
|
}
|
|
1231
1240
|
return null;
|
|
1232
1241
|
}
|
|
1242
|
+
/**
|
|
1243
|
+
* A time_expression formula outside the engine's grammar parses to nothing, and the relation then does nothing with
|
|
1244
|
+
* success reported (#39: Math.sin(...) from JavaScript habit, no motion). Names are checked against the grammar the
|
|
1245
|
+
* engine publishes (engine-lists, generated from expr-to-ir.js); syntax is the engine's to judge.
|
|
1246
|
+
*/
|
|
1247
|
+
function expressionProblem(expr) {
|
|
1248
|
+
const allowed = new Set([...Object.keys(EXPRESSION_FUNCTION_ARITY), ...EXPRESSION_CONSTANTS, 't', 'v']);
|
|
1249
|
+
const grammar = expressionGrammarText();
|
|
1250
|
+
for (let i = 0; i < expr.length;) {
|
|
1251
|
+
const c = expr[i];
|
|
1252
|
+
if (/[0-9]/.test(c) || (c === '.' && /[0-9]/.test(expr[i + 1] ?? ''))) {
|
|
1253
|
+
i++;
|
|
1254
|
+
while (i < expr.length && /[0-9.]/.test(expr[i]))
|
|
1255
|
+
i++;
|
|
1256
|
+
if (/[eE]/.test(expr[i] ?? '') && /[0-9+-]/.test(expr[i + 1] ?? '')) {
|
|
1257
|
+
i += 2;
|
|
1258
|
+
while (i < expr.length && /[0-9]/.test(expr[i]))
|
|
1259
|
+
i++;
|
|
1260
|
+
}
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
if (/[A-Za-z_]/.test(c)) {
|
|
1264
|
+
let j = i + 1;
|
|
1265
|
+
while (j < expr.length && /[A-Za-z0-9_]/.test(expr[j]))
|
|
1266
|
+
j++;
|
|
1267
|
+
const name = expr.slice(i, j);
|
|
1268
|
+
if (expr[j] === '.') {
|
|
1269
|
+
const member = /^[A-Za-z_][A-Za-z0-9_]*/.exec(expr.slice(j + 1))?.[0] ?? '';
|
|
1270
|
+
return `expression ${JSON.stringify(expr)} uses ${name}.${member}, and the formula is not JavaScript: it would parse to nothing and the item would not move. Write ${member || 'the function'}(…) without ${name}. The grammar: ${grammar}.`;
|
|
1271
|
+
}
|
|
1272
|
+
if (EXPRESSION_REJECTED.includes(name)) {
|
|
1273
|
+
return `expression ${JSON.stringify(expr)} uses ${name}(), which the engine refuses: a formula must give the same value at the same time in every export. Use noise(t) for a varying value that repeats.`;
|
|
1274
|
+
}
|
|
1275
|
+
const arity = EXPRESSION_FUNCTION_ARITY[name];
|
|
1276
|
+
if (arity !== undefined) {
|
|
1277
|
+
// Count the call's top-level arguments: a wrong count parses to nothing as well (min(1, 2, 3)).
|
|
1278
|
+
let k = j;
|
|
1279
|
+
while (expr[k] === ' ')
|
|
1280
|
+
k++;
|
|
1281
|
+
if (expr[k] === '(') {
|
|
1282
|
+
let depth = 0, args = 0, any = false;
|
|
1283
|
+
for (let q = k; q < expr.length; q++) {
|
|
1284
|
+
const ch = expr[q];
|
|
1285
|
+
if (ch === '(') {
|
|
1286
|
+
depth++;
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
if (ch === ')') {
|
|
1290
|
+
depth--;
|
|
1291
|
+
if (depth === 0)
|
|
1292
|
+
break;
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
if (depth === 1 && ch === ',')
|
|
1296
|
+
args++;
|
|
1297
|
+
if (depth >= 1 && ch.trim())
|
|
1298
|
+
any = true;
|
|
1299
|
+
}
|
|
1300
|
+
const given = any ? args + 1 : 0;
|
|
1301
|
+
if (given !== arity)
|
|
1302
|
+
return `expression ${JSON.stringify(expr)} calls ${name} with ${given} argument${given === 1 ? '' : 's'}; it takes ${arity}, and any other count parses to nothing, so the item would not move.`;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
if (!allowed.has(name)) {
|
|
1306
|
+
const near = [...allowed].find((a) => a.toLowerCase() === name.toLowerCase());
|
|
1307
|
+
return `expression ${JSON.stringify(expr)} names ${name}, which the formula grammar does not have, so it would parse to nothing and the item would not move.${near ? ` Did you mean ${near}?` : ''} The grammar: ${grammar}.`;
|
|
1308
|
+
}
|
|
1309
|
+
i = j;
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
i++;
|
|
1313
|
+
}
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* A relationOptions key the rule never reads, said instead of accepted in silence. The list is the engine's own
|
|
1318
|
+
* (RELATION_PARAM_KEYS: what each rule declares or reads); a rule without one (custom, event reactions) gets no
|
|
1319
|
+
* warning. `injected` are keys the tool added itself (a name pattern's index and count).
|
|
1320
|
+
*/
|
|
1321
|
+
/** Levenshtein distance, for a "did you mean" on a misspelt key. */
|
|
1322
|
+
function editDistance(a, b) {
|
|
1323
|
+
const row = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
1324
|
+
for (let i = 1; i <= a.length; i++) {
|
|
1325
|
+
let prev = row[0];
|
|
1326
|
+
row[0] = i;
|
|
1327
|
+
for (let j = 1; j <= b.length; j++) {
|
|
1328
|
+
const t = row[j];
|
|
1329
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
1330
|
+
prev = t;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return row[b.length];
|
|
1334
|
+
}
|
|
1335
|
+
export function relationOptionWarning(relationType, params, injected = []) {
|
|
1336
|
+
const keys = typeof relationType === 'string' ? RELATION_PARAM_KEYS[relationType] : undefined;
|
|
1337
|
+
if (!keys || !params || typeof params !== 'object')
|
|
1338
|
+
return null;
|
|
1339
|
+
const unknown = Object.keys(params).filter((k) => !keys.includes(k) && !injected.includes(k));
|
|
1340
|
+
if (!unknown.length)
|
|
1341
|
+
return null;
|
|
1342
|
+
const norm = (x) => x.replace(/_/g, '').toLowerCase();
|
|
1343
|
+
const hint = unknown.map((k) => keys.find((x) => norm(x) === norm(k)) ?? keys.find((x) => editDistance(norm(x), norm(k)) <= (k.length > 5 ? 2 : 1))).map((x, i) => (x ? `${unknown[i]} → ${x}?` : '')).filter(Boolean);
|
|
1344
|
+
return `${relationType} does not read ${unknown.join(', ')}, so ${unknown.length > 1 ? 'they were' : 'it was'} ignored${hint.length ? ` (${hint.join('; ')})` : ''}. ${relationType} reads: ${keys.join(', ')}.`;
|
|
1345
|
+
}
|
|
1233
1346
|
const VISIBILITY_REACTION = /^on_(event|enter|exit)_set_visibility$/;
|
|
1234
1347
|
/**
|
|
1235
1348
|
* A visibility reaction reads params.visible and nothing else: absent means show. `value`, which the other set_*
|
|
@@ -1249,23 +1362,23 @@ function generateAddRelationCode(sourceId, targetId, relationType, params) {
|
|
|
1249
1362
|
// an item, the camera, a cursor target — is asked first here, after the same
|
|
1250
1363
|
// '#id' forgiveness the engine applies.
|
|
1251
1364
|
return `
|
|
1252
|
-
// Add ${relationType} relation: ${sourceId} -> ${targetId}
|
|
1365
|
+
// Add ${oneLine(relationType)} relation: ${oneLine(sourceId)} -> ${oneLine(targetId)}
|
|
1253
1366
|
(function() {
|
|
1254
1367
|
const norm = function(id) { return typeof app._resolveRelationEndpoint === 'function' ? app._resolveRelationEndpoint(id) : id; };
|
|
1255
1368
|
if (typeof app._isKnownRelationEndpoint === 'function') {
|
|
1256
1369
|
const bad = [${JSON.stringify(sourceId)}, ${JSON.stringify(targetId)}].filter(function(id) { return !app._isKnownRelationEndpoint(norm(id)); });
|
|
1257
1370
|
if (bad.length) {
|
|
1258
|
-
return { success: false, relationType:
|
|
1371
|
+
return { success: false, relationType: ${jsLiteral(relationType)}, error: bad.map(function(id) { return JSON.stringify(id); }).join(' and ')
|
|
1259
1372
|
+ ' ' + (bad.length > 1 ? 'are not items' : 'is not an item') + ' on the canvas, so the relation would be stored and skipped every frame. Check the id (pinepaper_get_items lists them).' };
|
|
1260
1373
|
}
|
|
1261
1374
|
}
|
|
1262
|
-
const success = app.addRelation(
|
|
1375
|
+
const success = app.addRelation(${jsLiteral(sourceId)}, ${jsLiteral(targetId)}, ${jsLiteral(relationType)}, ${JSON.stringify(params, null, 2)});
|
|
1263
1376
|
if (!success) {
|
|
1264
1377
|
const __why = ${relationRefusalReasonJs(jsLiteral(relationType))};
|
|
1265
|
-
return { success: false, relationType:
|
|
1378
|
+
return { success: false, relationType: ${jsLiteral(relationType)}, error: 'the studio refused the ' + ${jsLiteral(relationType)} + ' relation between ' + ${jsLiteral(sourceId)} + ' and ' + ${jsLiteral(targetId)} + (__why ? ': ' + __why : '.') };
|
|
1266
1379
|
}
|
|
1267
1380
|
app.historyManager.saveState();
|
|
1268
|
-
return { success: true, sourceId:
|
|
1381
|
+
return { success: true, sourceId: ${jsLiteral(sourceId)}, targetId: ${jsLiteral(targetId)}, relationType: ${jsLiteral(relationType)}${(() => { const w = relationOptionWarning(relationType, params); return w ? `, warning: ${jsLiteral(w)}` : ''; })()} };
|
|
1269
1382
|
})();
|
|
1270
1383
|
`.trim();
|
|
1271
1384
|
}
|
|
@@ -1423,7 +1536,7 @@ function generateMovesAlongPathCode(sourceId, targetId, raw) {
|
|
|
1423
1536
|
const hasDuration = typeof duration === 'number' && duration > 0;
|
|
1424
1537
|
const hasDelay = typeof delay === 'number' && delay > 0;
|
|
1425
1538
|
return `
|
|
1426
|
-
// moves_along_path: ${sourceId}${ref ? ` along ${ref}` : ''}
|
|
1539
|
+
// moves_along_path: ${oneLine(sourceId)}${ref ? ` along ${oneLine(ref)}` : ''}
|
|
1427
1540
|
(function() {
|
|
1428
1541
|
const params = ${JSON.stringify(params)};
|
|
1429
1542
|
const hasPoints = Array.isArray(params.path) && params.path.length >= 2;
|
|
@@ -1537,14 +1650,14 @@ function aliasGeneratorParams(name, params) {
|
|
|
1537
1650
|
* Template for removing relations
|
|
1538
1651
|
*/
|
|
1539
1652
|
function generateRemoveRelationCode(sourceId, targetId, relationType) {
|
|
1540
|
-
const relationArg = relationType ?
|
|
1653
|
+
const relationArg = relationType ? jsLiteral(relationType) : 'undefined';
|
|
1541
1654
|
return `
|
|
1542
|
-
// Remove relation: ${sourceId} -> ${targetId}${relationType ? ` (${relationType})` : ''}
|
|
1543
|
-
app.removeRelation(
|
|
1655
|
+
// Remove relation: ${oneLine(sourceId)} -> ${oneLine(targetId)}${relationType ? ` (${oneLine(relationType)})` : ''}
|
|
1656
|
+
app.removeRelation(${jsLiteral(sourceId)}, ${jsLiteral(targetId)}, ${relationArg});
|
|
1544
1657
|
app.historyManager.saveState();
|
|
1545
1658
|
|
|
1546
1659
|
// Return success
|
|
1547
|
-
({ success: true, sourceId:
|
|
1660
|
+
({ success: true, sourceId: ${jsLiteral(sourceId)}, targetId: ${jsLiteral(targetId)} });
|
|
1548
1661
|
`.trim();
|
|
1549
1662
|
}
|
|
1550
1663
|
/**
|
|
@@ -1553,7 +1666,7 @@ app.historyManager.saveState();
|
|
|
1553
1666
|
function generateQueryRelationsCode(itemId, relationType, direction = 'outgoing') {
|
|
1554
1667
|
const ID = jsLiteral(itemId); // caller text: a quoted literal, never spliced raw
|
|
1555
1668
|
if (direction === 'outgoing') {
|
|
1556
|
-
const typeArg = relationType ?
|
|
1669
|
+
const typeArg = relationType ? jsLiteral(relationType) : 'undefined';
|
|
1557
1670
|
return `
|
|
1558
1671
|
// Query outgoing relations for ${ID}
|
|
1559
1672
|
const relations = app.getRelations(${ID}, ${typeArg});
|
|
@@ -1574,7 +1687,7 @@ const formatted = relations.map(r => ({
|
|
|
1574
1687
|
`.trim();
|
|
1575
1688
|
}
|
|
1576
1689
|
else {
|
|
1577
|
-
const typeArg = relationType ?
|
|
1690
|
+
const typeArg = relationType ? jsLiteral(relationType) : 'undefined';
|
|
1578
1691
|
return `
|
|
1579
1692
|
// Query incoming relations for ${ID}
|
|
1580
1693
|
const results = app.queryByRelationTarget(${ID}, ${typeArg});
|
|
@@ -1586,7 +1699,7 @@ const relations = results.map(r => ({
|
|
|
1586
1699
|
// queryByTarget returns the real relation name on each result. Hardcoding
|
|
1587
1700
|
// the filter string labelled every edge 'unknown' on an unfiltered query —
|
|
1588
1701
|
// which is exactly the query you make when exploring an unfamiliar graph.
|
|
1589
|
-
relationType: r.relation || ${relationType ?
|
|
1702
|
+
relationType: r.relation || ${relationType ? jsLiteral(relationType) : "'unknown'"},
|
|
1590
1703
|
params: r.params || {}
|
|
1591
1704
|
}));
|
|
1592
1705
|
|
|
@@ -1602,13 +1715,13 @@ function generateAnimateCode(itemId, animationType, speed, intensity, delay) {
|
|
|
1602
1715
|
const extra = (intensity !== undefined ? `,\n animationIntensity: ${intensity}` : '') +
|
|
1603
1716
|
(delay !== undefined ? `,\n animationDelay: ${delay}` : '');
|
|
1604
1717
|
return `
|
|
1605
|
-
// Apply ${animationType} animation to ${ID}
|
|
1718
|
+
// Apply ${oneLine(animationType)} animation to ${ID}
|
|
1606
1719
|
const item = app.getItemById(${ID});
|
|
1607
1720
|
if (!item) {
|
|
1608
1721
|
throw new Error('Item not found: ' + ${ID});
|
|
1609
1722
|
}
|
|
1610
1723
|
app.animate(item, {
|
|
1611
|
-
animationType:
|
|
1724
|
+
animationType: ${jsLiteral(animationType)},
|
|
1612
1725
|
animationSpeed: ${speed}${extra}
|
|
1613
1726
|
});
|
|
1614
1727
|
|
|
@@ -1627,7 +1740,7 @@ app.animate(item, {
|
|
|
1627
1740
|
known: r.known,
|
|
1628
1741
|
};
|
|
1629
1742
|
}
|
|
1630
|
-
return { success: true, itemId: ${ID}, animationType:
|
|
1743
|
+
return { success: true, itemId: ${ID}, animationType: ${jsLiteral(animationType)} };
|
|
1631
1744
|
})();
|
|
1632
1745
|
`.trim();
|
|
1633
1746
|
}
|
|
@@ -1868,7 +1981,7 @@ function generateExecuteGeneratorCode(generatorName, params, region) {
|
|
|
1868
1981
|
? ` in region ${region.width}×${region.height} @ (${region.x},${region.y})`
|
|
1869
1982
|
: ` in the shape of ${region.shape.itemId}`;
|
|
1870
1983
|
return `
|
|
1871
|
-
// Execute ${generatorName} generator${where}
|
|
1984
|
+
// Execute ${oneLine(generatorName)} generator${oneLine(where)}
|
|
1872
1985
|
(async function() {${emitGeneratorRun(generatorName, JSON.stringify(params, null, 2), region)}
|
|
1873
1986
|
})();
|
|
1874
1987
|
`.trim();
|
|
@@ -1895,9 +2008,9 @@ function emitGeneratorRun(generatorName, paramsJson, region) {
|
|
|
1895
2008
|
if (!__si) return { success: false, error: ${JSON.stringify(`region.shape names no item "${itemId}" — nothing was drawn. List items with pinepaper_get_items.`)} };
|
|
1896
2009
|
if (__si.className !== 'Path' && __si.className !== 'CompoundPath') return { success: false, error: ${JSON.stringify(`region.shape item "${itemId}" is a `)} + __si.className + ', and a clip needs a path (a shape, not text, a group or an image) — nothing was drawn.' };
|
|
1897
2010
|
}` : ''}
|
|
1898
|
-
await app.executeGenerator(
|
|
2011
|
+
await app.executeGenerator(${jsLiteral(generatorName)}, ${paramsJson}, ${options});${shaped ? `
|
|
1899
2012
|
const __shapes = !!(app.generatorRegistry && typeof app.generatorRegistry._regionClipPath === 'function');` : ''}
|
|
1900
|
-
return { success: true, generator:
|
|
2013
|
+
return { success: true, generator: ${jsLiteral(generatorName)}${region ? `, region: ${JSON.stringify(region)}` : ''}${shaped ? `, ...(__shapes ? {} : { regionShapeApplied: false, warning: ${JSON.stringify(`this studio does not clip generator regions to a shape, so ${typeof region.shape === 'object' ? 'the region had no box and the generator drew across the whole canvas' : `the ${region.shape} was drawn as a rectangle`}. It needs an engine with region shapes.`)} })` : ''} };`;
|
|
1901
2014
|
}
|
|
1902
2015
|
/**
|
|
1903
2016
|
* Template for applying effects
|
|
@@ -1905,14 +2018,14 @@ function emitGeneratorRun(generatorName, paramsJson, region) {
|
|
|
1905
2018
|
function generateApplyEffectCode(itemId, effectType, params) {
|
|
1906
2019
|
const ID = jsLiteral(itemId); // caller text: a quoted literal, never spliced raw
|
|
1907
2020
|
return `
|
|
1908
|
-
// Apply ${effectType} effect to ${ID}
|
|
2021
|
+
// Apply ${oneLine(effectType)} effect to ${ID}
|
|
1909
2022
|
const item = app.getItemById(${ID});
|
|
1910
2023
|
if (!item) {
|
|
1911
2024
|
throw new Error('Item not found: ' + ${ID});
|
|
1912
2025
|
}
|
|
1913
|
-
app.applyEffect(item,
|
|
2026
|
+
app.applyEffect(item, ${jsLiteral(effectType)}, ${JSON.stringify(params, null, 2)});
|
|
1914
2027
|
|
|
1915
|
-
({ success: true, itemId: ${ID}, effectType:
|
|
2028
|
+
({ success: true, itemId: ${ID}, effectType: ${jsLiteral(effectType)} });
|
|
1916
2029
|
`.trim();
|
|
1917
2030
|
}
|
|
1918
2031
|
/**
|
|
@@ -1964,9 +2077,9 @@ function generateGetItemsCode(filter) {
|
|
|
1964
2077
|
// conditions, not on the keys.
|
|
1965
2078
|
const conditions = [];
|
|
1966
2079
|
if (filter?.type)
|
|
1967
|
-
conditions.push(`entry.type ===
|
|
2080
|
+
conditions.push(`entry.type === ${jsLiteral(filter.type)}`);
|
|
1968
2081
|
if (filter?.source)
|
|
1969
|
-
conditions.push(`entry.source ===
|
|
2082
|
+
conditions.push(`entry.source === ${jsLiteral(filter.source)}`);
|
|
1970
2083
|
if (filter?.hasAnimation !== undefined) {
|
|
1971
2084
|
// 'none' is what the engine stores on a still item, and !!'none' is true
|
|
1972
2085
|
// (gate H5): animated means a real loop animation OR keyframes.
|
|
@@ -2163,8 +2276,8 @@ app.setPlaybackTime(${time || 0});
|
|
|
2163
2276
|
function generateSetBackgroundColorCode(color) {
|
|
2164
2277
|
return `
|
|
2165
2278
|
// Set background color
|
|
2166
|
-
app.setBackgroundColor(
|
|
2167
|
-
({ success: true, color:
|
|
2279
|
+
app.setBackgroundColor(${jsLiteral(color)});
|
|
2280
|
+
({ success: true, color: ${jsLiteral(color)} });
|
|
2168
2281
|
`.trim();
|
|
2169
2282
|
}
|
|
2170
2283
|
/**
|
|
@@ -2671,7 +2784,7 @@ ${body}
|
|
|
2671
2784
|
} catch (e) { errors.push({ index: ${i}, error: (e && e.message) || String(e) }); }`;
|
|
2672
2785
|
});
|
|
2673
2786
|
return `
|
|
2674
|
-
// Batch create ${items.length} items — create_item for each, one history save
|
|
2787
|
+
// Batch create ${oneLine(items.length)} items — create_item for each, one history save
|
|
2675
2788
|
(async function() {
|
|
2676
2789
|
const results = [];
|
|
2677
2790
|
const errors = [];
|
|
@@ -2704,7 +2817,7 @@ function generateBatchModifyCode(modifications) {
|
|
|
2704
2817
|
} catch (e) { skipped.push({ itemId: ${JSON.stringify(m.itemId)}, reason: (e && e.message) || String(e) }); }`;
|
|
2705
2818
|
});
|
|
2706
2819
|
return `
|
|
2707
|
-
// Batch modify ${modifications.length} items — modify_item for each, one history save
|
|
2820
|
+
// Batch modify ${oneLine(modifications.length)} items — modify_item for each, one history save
|
|
2708
2821
|
(async function() {
|
|
2709
2822
|
let count = 0;
|
|
2710
2823
|
const skipped = [];
|
|
@@ -2726,11 +2839,11 @@ ${blocks.join('\n')}
|
|
|
2726
2839
|
*/
|
|
2727
2840
|
function generateCreateGridCode(cols, rows, strokeColor, strokeWidth, animated, waveSpeed, waveAmplitude) {
|
|
2728
2841
|
return `
|
|
2729
|
-
// Create grid with ${cols} columns and ${rows} rows
|
|
2842
|
+
// Create grid with ${oneLine(cols)} columns and ${oneLine(rows)} rows
|
|
2730
2843
|
const grid = app.createGrid({
|
|
2731
2844
|
cols: ${cols},
|
|
2732
2845
|
rows: ${rows},
|
|
2733
|
-
strokeColor:
|
|
2846
|
+
strokeColor: ${jsLiteral(strokeColor)},
|
|
2734
2847
|
strokeWidth: ${strokeWidth},
|
|
2735
2848
|
animated: ${animated},
|
|
2736
2849
|
waveSpeed: ${waveSpeed},
|
|
@@ -3119,7 +3232,7 @@ export class PinePaperCodeGenerator {
|
|
|
3119
3232
|
// than read as a single string, which would render "[object Object]".
|
|
3120
3233
|
if (validated.presetId) {
|
|
3121
3234
|
return `
|
|
3122
|
-
// Adopt relation preset
|
|
3235
|
+
// Adopt relation preset ${jsLiteral(validated.presetId)} on ${jsLiteral(validated.sourceId)}
|
|
3123
3236
|
(async function() {
|
|
3124
3237
|
if (typeof app.applyRelationPreset !== 'function') { return { success: false, error: 'app.applyRelationPreset unavailable — update FxTool' }; }
|
|
3125
3238
|
const r = await app.applyRelationPreset(${JSON.stringify(validated.sourceId)}, ${JSON.stringify(validated.presetId)}, ${JSON.stringify(validated.presetValues ?? {})});
|
|
@@ -3149,7 +3262,7 @@ export class PinePaperCodeGenerator {
|
|
|
3149
3262
|
if (validated.targetId)
|
|
3150
3263
|
return code;
|
|
3151
3264
|
return code
|
|
3152
|
-
.replace(`return { success: true, sourceId:
|
|
3265
|
+
.replace(`return { success: true, sourceId: ${jsLiteral(validated.sourceId)}, targetId: ${jsLiteral(validated.sourceId)},`, `return { success: true, sourceId: ${jsLiteral(validated.sourceId)}, targetId: null,`)
|
|
3153
3266
|
.replace(`relation between ${validated.sourceId} and ${validated.sourceId}.`, `relation on ${validated.sourceId} (no target).`);
|
|
3154
3267
|
}
|
|
3155
3268
|
/**
|
|
@@ -3313,7 +3426,7 @@ ${asReturningBody(code)}
|
|
|
3313
3426
|
: `{ item: app.importSVG(${varName}), changes: [] }`;
|
|
3314
3427
|
if (url) {
|
|
3315
3428
|
return `
|
|
3316
|
-
// Import SVG from URL${source === 'figma' ? ' (Figma-normalised)' : ''}
|
|
3429
|
+
// Import SVG from URL${oneLine(source === 'figma' ? ' (Figma-normalised)' : '')}
|
|
3317
3430
|
// WRAPPED, because these were bare top-level statements with an await. That
|
|
3318
3431
|
// parses under the governor, which builds an AsyncFunction, and is a SYNTAX
|
|
3319
3432
|
// ERROR on the eval fallback — so importing an SVG by URL never ran at all on
|
|
@@ -3322,12 +3435,12 @@ ${asReturningBody(code)}
|
|
|
3322
3435
|
(async function() {
|
|
3323
3436
|
let response;
|
|
3324
3437
|
try {
|
|
3325
|
-
response = await fetch(
|
|
3438
|
+
response = await fetch(${jsLiteral(url)});
|
|
3326
3439
|
} catch (netErr) {
|
|
3327
|
-
return { success: false, error: 'could not reach ${url} — ' + ((netErr && netErr.message) || 'network request failed') };
|
|
3440
|
+
return { success: false, error: 'could not reach ' + ${jsLiteral(url)} + ' — ' + ((netErr && netErr.message) || 'network request failed') };
|
|
3328
3441
|
}
|
|
3329
3442
|
if (!response.ok) {
|
|
3330
|
-
return { success: false, error: 'the server refused ${url} — HTTP ' + response.status + ' ' + (response.statusText || ''), status: response.status };
|
|
3443
|
+
return { success: false, error: 'the server refused ' + ${jsLiteral(url)} + ' — HTTP ' + response.status + ' ' + (response.statusText || ''), status: response.status };
|
|
3331
3444
|
}
|
|
3332
3445
|
let svgText = await response.text();
|
|
3333
3446
|
${INLINE_REMOTE_IMAGES}
|
|
@@ -3336,7 +3449,7 @@ ${INLINE_REMOTE_IMAGES}
|
|
|
3336
3449
|
const _r = ${importExpr('svgText')};
|
|
3337
3450
|
const imported = _r.item;
|
|
3338
3451
|
if (!imported) {
|
|
3339
|
-
return { success: false, error: 'the response from ${url} did not parse as SVG. Check the URL serves the file itself rather than a page around it.' };
|
|
3452
|
+
return { success: false, error: 'the response from ' + ${jsLiteral(url)} + ' did not parse as SVG. Check the URL serves the file itself rather than a page around it.' };
|
|
3340
3453
|
}
|
|
3341
3454
|
imported.position = new paper.Point(${position.x}, ${position.y});
|
|
3342
3455
|
imported.scale(${scale});
|
|
@@ -3360,7 +3473,7 @@ ${INLINE_REMOTE_IMAGES}
|
|
|
3360
3473
|
// Escape the SVG string for embedding in code
|
|
3361
3474
|
const escapedSvg = svgString.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
3362
3475
|
return `
|
|
3363
|
-
// Import SVG string${source === 'figma' ? ' (Figma-normalised)' : ''}
|
|
3476
|
+
// Import SVG string${oneLine(source === 'figma' ? ' (Figma-normalised)' : '')}
|
|
3364
3477
|
(async function() {
|
|
3365
3478
|
let svgString = \`${escapedSvg}\`;
|
|
3366
3479
|
${INLINE_REMOTE_IMAGES}
|
|
@@ -3542,9 +3655,9 @@ throw new Error('Either svgString or url must be provided');
|
|
|
3542
3655
|
*/
|
|
3543
3656
|
generateAddFilter(filterType, params = {}) {
|
|
3544
3657
|
return `
|
|
3545
|
-
// Add ${filterType} filter
|
|
3546
|
-
const filterId = app.filterSystem.addFilter(
|
|
3547
|
-
({ success: true, filterId, filterType:
|
|
3658
|
+
// Add ${oneLine(filterType)} filter
|
|
3659
|
+
const filterId = app.filterSystem.addFilter(${jsLiteral(filterType)}, ${JSON.stringify(params, null, 2)});
|
|
3660
|
+
({ success: true, filterId, filterType: ${jsLiteral(filterType)} });
|
|
3548
3661
|
`.trim();
|
|
3549
3662
|
}
|
|
3550
3663
|
/**
|
|
@@ -3573,7 +3686,7 @@ const filterId = app.filterSystem.addFilter('${filterType}', ${JSON.stringify(pa
|
|
|
3573
3686
|
return `
|
|
3574
3687
|
// Create glossy 3D sphere effect
|
|
3575
3688
|
const sphereGroup = new paper.Group({ parent: app.textItemGroup });
|
|
3576
|
-
const baseColor = new paper.Color(
|
|
3689
|
+
const baseColor = new paper.Color(${jsLiteral(baseColor)});
|
|
3577
3690
|
|
|
3578
3691
|
${castShadow ? `// Shadow underneath
|
|
3579
3692
|
const shadow = new paper.Path.Ellipse({
|
|
@@ -3753,7 +3866,7 @@ paper.project.activeLayer.children.forEach(c => {
|
|
|
3753
3866
|
const bg = new paper.Path.Rectangle({
|
|
3754
3867
|
point: [0, 0],
|
|
3755
3868
|
size: [paper.view.size.width, paper.view.size.height],
|
|
3756
|
-
fillColor:
|
|
3869
|
+
fillColor: ${jsLiteral(backgroundColor)},
|
|
3757
3870
|
name: 'background'
|
|
3758
3871
|
});
|
|
3759
3872
|
bg.sendToBack();
|
|
@@ -3763,7 +3876,7 @@ bg.sendToBack();
|
|
|
3763
3876
|
if (backgroundGenerator) {
|
|
3764
3877
|
codeParts.push(`
|
|
3765
3878
|
// Execute background generator
|
|
3766
|
-
const _gen = app.generatorRegistry?.get(
|
|
3879
|
+
const _gen = app.generatorRegistry?.get(${jsLiteral(backgroundGenerator)});
|
|
3767
3880
|
if (typeof _gen === 'function') {
|
|
3768
3881
|
_gen();
|
|
3769
3882
|
} else if (_gen && typeof _gen.generate === 'function') {
|
|
@@ -3795,20 +3908,20 @@ if (typeof _gen === 'function') {
|
|
|
3795
3908
|
...properties,
|
|
3796
3909
|
};
|
|
3797
3910
|
codeParts.push(`
|
|
3798
|
-
// Create item: ${name}
|
|
3911
|
+
// Create item: ${oneLine(name)}
|
|
3799
3912
|
(function() {
|
|
3800
|
-
const item = app.create(
|
|
3913
|
+
const item = app.create(${jsLiteral(itemType)}, ${JSON.stringify(sceneParams)});
|
|
3801
3914
|
if (!item) {
|
|
3802
|
-
results.items.push({ name:
|
|
3915
|
+
results.items.push({ name: ${jsLiteral(name)}, error: 'app.create returned nothing for type ' + ${jsLiteral(itemType)} + ' — check the type and its properties.' });
|
|
3803
3916
|
return;
|
|
3804
3917
|
}
|
|
3805
3918
|
// create() registers and mints the id; only fall back for an older studio.
|
|
3806
3919
|
const itemId = (item.data && item.data.id)
|
|
3807
3920
|
? item.data.id
|
|
3808
|
-
: app.registerItem(item,
|
|
3921
|
+
: app.registerItem(item, ${jsLiteral(itemType)}, { source: 'mcp-scene' });
|
|
3809
3922
|
if (item.bringToFront) item.bringToFront();
|
|
3810
|
-
nameToId[
|
|
3811
|
-
results.items.push({ name:
|
|
3923
|
+
nameToId[${jsLiteral(name)}] = itemId;
|
|
3924
|
+
results.items.push({ name: ${jsLiteral(name)}, itemId, type: ${jsLiteral(itemType)} });
|
|
3812
3925
|
})();`);
|
|
3813
3926
|
}
|
|
3814
3927
|
// Establish relations
|
|
@@ -3818,25 +3931,25 @@ if (typeof _gen === 'function') {
|
|
|
3818
3931
|
const specProblem = relationSpecProblem(type, params);
|
|
3819
3932
|
if (specProblem) {
|
|
3820
3933
|
codeParts.push(`
|
|
3821
|
-
// Refused relation: ${source} ${type} ${target}
|
|
3934
|
+
// Refused relation: ${oneLine(source)} ${oneLine(type)} ${oneLine(target)}
|
|
3822
3935
|
(results.relationsRefused = results.relationsRefused || []).push(${JSON.stringify({ source, target, type, reason: specProblem })});
|
|
3823
3936
|
results.success = false;
|
|
3824
3937
|
results.error = 'refused ' + results.relationsRefused.length + ' relation(s): ' + results.relationsRefused.map(function(r) { return r.type + ' ' + r.source + ' -> ' + r.target + ' (' + r.reason + ')'; }).join('; ') + '. The items were created.';`);
|
|
3825
3938
|
continue;
|
|
3826
3939
|
}
|
|
3827
3940
|
codeParts.push(`
|
|
3828
|
-
// Add relation: ${source} ${type} ${target}
|
|
3941
|
+
// Add relation: ${oneLine(source)} ${oneLine(type)} ${oneLine(target)}
|
|
3829
3942
|
(function() {
|
|
3830
|
-
const sourceId = nameToId[
|
|
3831
|
-
const targetId = nameToId[
|
|
3943
|
+
const sourceId = nameToId[${jsLiteral(source)}];
|
|
3944
|
+
const targetId = nameToId[${jsLiteral(target)}];
|
|
3832
3945
|
if (sourceId && targetId) {
|
|
3833
3946
|
const params = ${paramsStr};
|
|
3834
3947
|
// A refused relation is not listed as made (owner rule 1).
|
|
3835
|
-
if (app.addRelation(sourceId, targetId,
|
|
3836
|
-
results.relations.push({ source:
|
|
3948
|
+
if (app.addRelation(sourceId, targetId, ${jsLiteral(type)}, params)) {
|
|
3949
|
+
results.relations.push({ source: ${jsLiteral(source)}, target: ${jsLiteral(target)}, type: ${jsLiteral(type)} });
|
|
3837
3950
|
} else {
|
|
3838
3951
|
const __why = ${relationRefusalReasonJs(jsLiteral(type))};
|
|
3839
|
-
(results.relationsRefused = results.relationsRefused || []).push({ source:
|
|
3952
|
+
(results.relationsRefused = results.relationsRefused || []).push({ source: ${jsLiteral(source)}, target: ${jsLiteral(target)}, type: ${jsLiteral(type)}, reason: __why || 'the studio refused it' });
|
|
3840
3953
|
results.success = false;
|
|
3841
3954
|
results.error = 'the studio refused ' + results.relationsRefused.length + ' relation(s): ' + results.relationsRefused.map(function(r) { return r.type + ' ' + r.source + ' -> ' + r.target + ' (' + r.reason + ')'; }).join('; ') + '. The items were created.';
|
|
3842
3955
|
}
|
|
@@ -3853,9 +3966,9 @@ results.error = 'refused ' + results.relationsRefused.length + ' relation(s): '
|
|
|
3853
3966
|
...(startTime !== undefined ? { animationDelay: startTime } : {}),
|
|
3854
3967
|
});
|
|
3855
3968
|
codeParts.push(`
|
|
3856
|
-
// Apply animation: ${type} to ${target}
|
|
3969
|
+
// Apply animation: ${oneLine(type)} to ${oneLine(target)}
|
|
3857
3970
|
(function() {
|
|
3858
|
-
const targetId = nameToId[
|
|
3971
|
+
const targetId = nameToId[${jsLiteral(target)}];
|
|
3859
3972
|
if (targetId) {
|
|
3860
3973
|
const targetItem = app.itemRegistry.get(targetId);
|
|
3861
3974
|
if (targetItem) {
|
|
@@ -3865,8 +3978,8 @@ results.error = 'refused ' + results.relationsRefused.length + ' relation(s): '
|
|
|
3865
3978
|
// loops with no end, so there is no duration to map and none is invented
|
|
3866
3979
|
// — staggering the STARTS is what makes a scene read as choreographed
|
|
3867
3980
|
// rather than everything moving at once from frame zero.
|
|
3868
|
-
app.animate(targetItem, { animationType:
|
|
3869
|
-
results.animations.push({ target:
|
|
3981
|
+
app.animate(targetItem, { animationType: ${jsLiteral(type)}, ...params });
|
|
3982
|
+
results.animations.push({ target: ${jsLiteral(target)}, type: ${jsLiteral(type)}${startTime !== undefined ? `, startTime: ${startTime}` : ''} });
|
|
3870
3983
|
}
|
|
3871
3984
|
}
|
|
3872
3985
|
})();
|
|
@@ -3900,7 +4013,7 @@ return results;
|
|
|
3900
4013
|
// ending in \ escapes the closing quote; 'x\\'; evil()' breaks out).
|
|
3901
4014
|
const labelStr = label ? JSON.stringify(label) : 'null';
|
|
3902
4015
|
return `
|
|
3903
|
-
// Create diagram shape: ${shapeType}
|
|
4016
|
+
// Create diagram shape: ${oneLine(shapeType)}
|
|
3904
4017
|
// POSITIONAL: createShape(shapeType, config). The type was being passed
|
|
3905
4018
|
// INSIDE the config object, where nothing reads it.
|
|
3906
4019
|
const shapeConfig = {
|
|
@@ -3911,12 +4024,12 @@ const shapeConfig = {
|
|
|
3911
4024
|
style: ${styleStr}
|
|
3912
4025
|
};
|
|
3913
4026
|
|
|
3914
|
-
const shape = app.diagramSystem.createShape(
|
|
3915
|
-
if (!shape) throw new Error('createShape returned nothing for shapeType
|
|
4027
|
+
const shape = app.diagramSystem.createShape(${jsLiteral(shapeType)}, shapeConfig);
|
|
4028
|
+
if (!shape) throw new Error('createShape returned nothing for shapeType ' + ${jsLiteral(shapeType)} + ' — call pinepaper_get_diagram_shapes for the registered types.');
|
|
3916
4029
|
const itemId = shape.data?.registryId || shape.id;
|
|
3917
4030
|
app.historyManager.saveState();
|
|
3918
4031
|
|
|
3919
|
-
({ itemId, shapeType:
|
|
4032
|
+
({ itemId, shapeType: ${jsLiteral(shapeType)}, position: { x: ${position.x}, y: ${position.y} } });
|
|
3920
4033
|
`.trim();
|
|
3921
4034
|
}
|
|
3922
4035
|
/**
|
|
@@ -3948,12 +4061,12 @@ app.historyManager.saveState();
|
|
|
3948
4061
|
if (label)
|
|
3949
4062
|
config.label = label;
|
|
3950
4063
|
return `
|
|
3951
|
-
// Connect items: ${sourceItemId} -> ${targetItemId}
|
|
3952
|
-
const sourceItem = app.getItemById(
|
|
3953
|
-
const targetItem = app.getItemById(
|
|
4064
|
+
// Connect items: ${oneLine(sourceItemId)} -> ${oneLine(targetItemId)}
|
|
4065
|
+
const sourceItem = app.getItemById(${jsLiteral(sourceItemId)});
|
|
4066
|
+
const targetItem = app.getItemById(${jsLiteral(targetItemId)});
|
|
3954
4067
|
|
|
3955
|
-
if (!sourceItem) throw new Error('Source item not found: ${sourceItemId}
|
|
3956
|
-
if (!targetItem) throw new Error('Target item not found: ${targetItemId}
|
|
4068
|
+
if (!sourceItem) throw new Error('Source item not found: ' + ${jsLiteral(sourceItemId)});
|
|
4069
|
+
if (!targetItem) throw new Error('Target item not found: ' + ${jsLiteral(targetItemId)});
|
|
3957
4070
|
|
|
3958
4071
|
const config = ${JSON.stringify(config, null, 2)};
|
|
3959
4072
|
const connector = app.diagramSystem.connect(sourceItem, targetItem, config);
|
|
@@ -3971,8 +4084,8 @@ app.historyManager.saveState();
|
|
|
3971
4084
|
// mismatch is REPORTED rather than left for a later call to trip over.
|
|
3972
4085
|
({
|
|
3973
4086
|
connectorId,
|
|
3974
|
-
sourceItemId:
|
|
3975
|
-
targetItemId:
|
|
4087
|
+
sourceItemId: ${jsLiteral(sourceItemId)},
|
|
4088
|
+
targetItemId: ${jsLiteral(targetItemId)},
|
|
3976
4089
|
...(${JSON.stringify(id ?? null)} && connectorId !== ${JSON.stringify(id ?? null)}
|
|
3977
4090
|
? { requestedId: ${JSON.stringify(id ?? null)},
|
|
3978
4091
|
idHonoured: false,
|
|
@@ -3994,12 +4107,12 @@ app.historyManager.saveState();
|
|
|
3994
4107
|
const merged = id ? { ...(config ?? {}), id } : config;
|
|
3995
4108
|
const configStr = merged ? JSON.stringify(merged, null, 2) : '{}';
|
|
3996
4109
|
return `
|
|
3997
|
-
// Connect ports: ${sourceItemId}:${sourcePort} -> ${targetItemId}:${targetPort}
|
|
3998
|
-
const sourceItem = app.getItemById(
|
|
3999
|
-
const targetItem = app.getItemById(
|
|
4110
|
+
// Connect ports: ${oneLine(sourceItemId)}:${oneLine(sourcePort)} -> ${oneLine(targetItemId)}:${oneLine(targetPort)}
|
|
4111
|
+
const sourceItem = app.getItemById(${jsLiteral(sourceItemId)});
|
|
4112
|
+
const targetItem = app.getItemById(${jsLiteral(targetItemId)});
|
|
4000
4113
|
|
|
4001
|
-
if (!sourceItem) throw new Error('Source item not found: ${sourceItemId}
|
|
4002
|
-
if (!targetItem) throw new Error('Target item not found: ${targetItemId}
|
|
4114
|
+
if (!sourceItem) throw new Error('Source item not found: ' + ${jsLiteral(sourceItemId)});
|
|
4115
|
+
if (!targetItem) throw new Error('Target item not found: ' + ${jsLiteral(targetItemId)});
|
|
4003
4116
|
|
|
4004
4117
|
const config = ${configStr};
|
|
4005
4118
|
// connectPorts(sourcePort, targetPort, config) takes PORT OBJECTS (gate C8,
|
|
@@ -4024,7 +4137,7 @@ if (!connector) { return { success: false, error: 'the diagram system did not cr
|
|
|
4024
4137
|
const connectorId = connector.id || connector.data?.registryId;
|
|
4025
4138
|
app.historyManager.saveState();
|
|
4026
4139
|
|
|
4027
|
-
({ connectorId, sourceItemId:
|
|
4140
|
+
({ connectorId, sourceItemId: ${jsLiteral(sourceItemId)}, sourcePort: ${jsLiteral(sourcePort)}, targetItemId: ${jsLiteral(targetItemId)}, targetPort: ${jsLiteral(targetPort)} });
|
|
4028
4141
|
`.trim();
|
|
4029
4142
|
}
|
|
4030
4143
|
/**
|
|
@@ -4042,22 +4155,22 @@ app.historyManager.saveState();
|
|
|
4042
4155
|
if (ports !== undefined || count !== undefined) {
|
|
4043
4156
|
const named = [ports !== undefined && 'ports', count !== undefined && 'count'].filter(Boolean).join(' and ');
|
|
4044
4157
|
throw new Error(`add_ports cannot honour ${named}: this engine lays out a whole port SET by type `
|
|
4045
|
-
+ `(portType
|
|
4158
|
+
+ `(portType ${jsLiteral(portType)}), with no per-port placement or count. Drop ${named}, or place `
|
|
4046
4159
|
+ 'individual connection points as items and join them with pinepaper_connect.');
|
|
4047
4160
|
}
|
|
4048
4161
|
return `
|
|
4049
|
-
// Add ports to item: ${itemId}
|
|
4050
|
-
const item = app.getItemById(
|
|
4051
|
-
if (!item) throw new Error('Item not found: ${itemId}
|
|
4162
|
+
// Add ports to item: ${oneLine(itemId)}
|
|
4163
|
+
const item = app.getItemById(${jsLiteral(itemId)});
|
|
4164
|
+
if (!item) throw new Error('Item not found: ' + ${jsLiteral(itemId)});
|
|
4052
4165
|
|
|
4053
4166
|
// The second argument is a TYPE STRING, not a config object. Passing an
|
|
4054
4167
|
// object made the engine iterate it as a list — "t.forEach is not a function".
|
|
4055
|
-
const result = app.diagramSystem.addPorts(item,
|
|
4168
|
+
const result = app.diagramSystem.addPorts(item, ${jsLiteral(portType)});
|
|
4056
4169
|
app.historyManager.saveState();
|
|
4057
4170
|
|
|
4058
4171
|
// addPorts returns the ports it made; older builds returned a count object.
|
|
4059
4172
|
const _added = Array.isArray(result) ? result.length : (result?.portsAdded ?? 0);
|
|
4060
|
-
({ itemId:
|
|
4173
|
+
({ itemId: ${jsLiteral(itemId)}, portsAdded: _added, portType: ${jsLiteral(portType)} });
|
|
4061
4174
|
`.trim();
|
|
4062
4175
|
}
|
|
4063
4176
|
/**
|
|
@@ -4069,7 +4182,7 @@ const _added = Array.isArray(result) ? result.length : (result?.portsAdded ?? 0)
|
|
|
4069
4182
|
const itemIdsStr = itemIds ? JSON.stringify(itemIds) : 'null';
|
|
4070
4183
|
const optionsStr = options ? JSON.stringify(options, null, 2) : '{}';
|
|
4071
4184
|
return `
|
|
4072
|
-
// Apply auto-layout: ${layoutType}
|
|
4185
|
+
// Apply auto-layout: ${oneLine(layoutType)}
|
|
4073
4186
|
// Wrapped: applyLayout is async, and a bare top-level await only works on the
|
|
4074
4187
|
// governed path. A paren-led IIFE also keeps the trailing-expression capture.
|
|
4075
4188
|
(async function() {
|
|
@@ -4085,10 +4198,10 @@ const items = itemIds
|
|
|
4085
4198
|
: (app.itemRegistry?.getAll?.() ?? []);
|
|
4086
4199
|
if (!items.length) throw new Error('auto_layout found no items to lay out' + (itemIds ? ' for the given itemIds' : ' on the canvas'));
|
|
4087
4200
|
|
|
4088
|
-
await app.diagramSystem.applyLayout(items,
|
|
4201
|
+
await app.diagramSystem.applyLayout(items, ${jsLiteral(layoutType)}, options);
|
|
4089
4202
|
app.historyManager.saveState();
|
|
4090
4203
|
|
|
4091
|
-
return { layoutType:
|
|
4204
|
+
return { layoutType: ${jsLiteral(layoutType)}, itemsAffected: items.length, success: true };
|
|
4092
4205
|
})();
|
|
4093
4206
|
`.trim();
|
|
4094
4207
|
}
|
|
@@ -4098,7 +4211,7 @@ return { layoutType: '${layoutType}', itemsAffected: items.length, success: true
|
|
|
4098
4211
|
generateGetDiagramShapes(input) {
|
|
4099
4212
|
const validated = GetDiagramShapesInputSchema.parse(input);
|
|
4100
4213
|
const { category } = validated;
|
|
4101
|
-
const categoryFilter = category ?
|
|
4214
|
+
const categoryFilter = category ? jsLiteral(category) : 'null';
|
|
4102
4215
|
return `
|
|
4103
4216
|
// Get available diagram shapes
|
|
4104
4217
|
const category = ${categoryFilter};
|
|
@@ -4117,7 +4230,7 @@ const shapes = category ? _lib.getByCategory(category) : _lib.getAll();
|
|
|
4117
4230
|
const validated = UpdateConnectorInputSchema.parse(input);
|
|
4118
4231
|
const { connectorId, style, label, labelPosition } = validated;
|
|
4119
4232
|
return `
|
|
4120
|
-
// Update connector: ${connectorId}
|
|
4233
|
+
// Update connector: ${oneLine(connectorId)}
|
|
4121
4234
|
(function() {
|
|
4122
4235
|
// CONNECTORS LIVE IN THE DIAGRAM SYSTEM, NOT THE ITEM REGISTRY (gate D1,
|
|
4123
4236
|
// 1.6.19): getItemById never resolves the id connect returned.
|
|
@@ -4169,7 +4282,7 @@ const shapes = category ? _lib.getByCategory(category) : _lib.getAll();
|
|
|
4169
4282
|
const validated = RemoveConnectorInputSchema.parse(input);
|
|
4170
4283
|
const { connectorId } = validated;
|
|
4171
4284
|
return `
|
|
4172
|
-
// Remove connector: ${connectorId}
|
|
4285
|
+
// Remove connector: ${oneLine(connectorId)}
|
|
4173
4286
|
// Resolved through the diagram system (gate D1, 1.6.19), as update_connector is.
|
|
4174
4287
|
const __cid = ${JSON.stringify(connectorId)};
|
|
4175
4288
|
const connector = (app.diagramSystem && app.diagramSystem.getConnector(__cid)) || app.getItemById(__cid);
|
|
@@ -4178,7 +4291,7 @@ if (!connector) throw new Error('Connector not found: ' + __cid);
|
|
|
4178
4291
|
app.diagramSystem.connectorManager.removeConnector(connector);
|
|
4179
4292
|
app.historyManager.saveState();
|
|
4180
4293
|
|
|
4181
|
-
({ connectorId:
|
|
4294
|
+
({ connectorId: ${jsLiteral(connectorId)}, removed: true });
|
|
4182
4295
|
`.trim();
|
|
4183
4296
|
}
|
|
4184
4297
|
/**
|
|
@@ -4207,17 +4320,17 @@ const isActive = app.diagramSystem.toggle();
|
|
|
4207
4320
|
({ action: 'toggle', active: isActive });
|
|
4208
4321
|
`.trim();
|
|
4209
4322
|
case 'setMode':
|
|
4210
|
-
const modeStr = mode ?
|
|
4211
|
-
const shapeStr = shapeType ? `,
|
|
4323
|
+
const modeStr = mode ? jsLiteral(mode) : "'select'";
|
|
4324
|
+
const shapeStr = shapeType ? `, ${jsLiteral(shapeType)}` : '';
|
|
4212
4325
|
return `
|
|
4213
4326
|
// Set diagram tool mode
|
|
4214
4327
|
app.diagramSystem.setMode(${modeStr}${shapeStr});
|
|
4215
|
-
({ action: 'setMode', mode: ${modeStr}${shapeType ? `, shapeType:
|
|
4328
|
+
({ action: 'setMode', mode: ${modeStr}${shapeType ? `, shapeType: ${jsLiteral(shapeType)}` : ''} });
|
|
4216
4329
|
`.trim();
|
|
4217
4330
|
default:
|
|
4218
4331
|
return `
|
|
4219
4332
|
// Unknown diagram mode action
|
|
4220
|
-
throw new Error('Unknown diagram mode action: ${action}
|
|
4333
|
+
throw new Error('Unknown diagram mode action: ' + ${jsLiteral(action)});
|
|
4221
4334
|
`.trim();
|
|
4222
4335
|
}
|
|
4223
4336
|
}
|
|
@@ -4239,7 +4352,7 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
4239
4352
|
(async function() {
|
|
4240
4353
|
const jobOptions = {
|
|
4241
4354
|
name: ${nameStr},
|
|
4242
|
-
screenshotPolicy:
|
|
4355
|
+
screenshotPolicy: ${jsLiteral(policyStr)},
|
|
4243
4356
|
agentMode: true
|
|
4244
4357
|
};
|
|
4245
4358
|
`;
|
|
@@ -4259,7 +4372,7 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
4259
4372
|
if (canvasPreset) {
|
|
4260
4373
|
code += `
|
|
4261
4374
|
// Set canvas size to preset (platform name → engine preset key)
|
|
4262
|
-
app.setCanvasSize(
|
|
4375
|
+
app.setCanvasSize(${jsLiteral(canvasPresetFor(canvasPreset))});
|
|
4263
4376
|
// Allow canvas resize to take effect
|
|
4264
4377
|
await new Promise(r => setTimeout(r, 50));
|
|
4265
4378
|
`;
|
|
@@ -4303,14 +4416,14 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
4303
4416
|
`;
|
|
4304
4417
|
}
|
|
4305
4418
|
code += `
|
|
4306
|
-
// Return job context with canvas size${wantOntology ? ' + initial ontology' : ''}
|
|
4419
|
+
// Return job context with canvas size${oneLine(wantOntology ? ' + initial ontology' : '')}
|
|
4307
4420
|
const _cs = app.getCanvasSize ? app.getCanvasSize() : { width: 800, height: 600 };
|
|
4308
4421
|
return {
|
|
4309
4422
|
success: true,
|
|
4310
4423
|
jobStarted: true,
|
|
4311
4424
|
name: ${nameStr},
|
|
4312
|
-
screenshotPolicy:
|
|
4313
|
-
canvasPreset: ${canvasPreset ?
|
|
4425
|
+
screenshotPolicy: ${jsLiteral(policyStr)},
|
|
4426
|
+
canvasPreset: ${canvasPreset ? jsLiteral(canvasPreset) : 'null'},
|
|
4314
4427
|
canvasCleared: ${shouldClear},
|
|
4315
4428
|
canvasSize: { width: _cs.width || 800, height: _cs.height || 600 }${canvasPreset ? '' : `,
|
|
4316
4429
|
...(_boundedFrom ? { note: 'the canvas was unbounded (it exported the artwork, not the board); it is now fixed at ' + (_cs.width || 800) + 'x' + (_cs.height || 600) + ', so stills and video both export that size.' } : {})`}${wantOntology ? `,
|
|
@@ -4510,7 +4623,7 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
4510
4623
|
if (canvasPreset) {
|
|
4511
4624
|
code += `
|
|
4512
4625
|
// Set canvas to preset (platform name → engine preset key)
|
|
4513
|
-
app.setCanvasSize(
|
|
4626
|
+
app.setCanvasSize(${jsLiteral(canvasPresetFor(canvasPreset))});
|
|
4514
4627
|
// Allow canvas resize to take effect
|
|
4515
4628
|
await new Promise(r => setTimeout(r, 50));
|
|
4516
4629
|
`;
|
|
@@ -4518,14 +4631,14 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
4518
4631
|
if (backgroundColor && !preserveBackground) {
|
|
4519
4632
|
code += `
|
|
4520
4633
|
// Set background color
|
|
4521
|
-
app.setBackgroundColor(
|
|
4634
|
+
app.setBackgroundColor(${jsLiteral(backgroundColor)});
|
|
4522
4635
|
`;
|
|
4523
4636
|
}
|
|
4524
4637
|
code += `
|
|
4525
4638
|
// Save state
|
|
4526
4639
|
if (app.historyManager) app.historyManager.saveState();
|
|
4527
4640
|
const _cs = app.getCanvasSize ? app.getCanvasSize() : { width: 800, height: 600 };
|
|
4528
|
-
return { success: true, reset: true, canvasPreset: ${canvasPreset ?
|
|
4641
|
+
return { success: true, reset: true, canvasPreset: ${canvasPreset ? jsLiteral(canvasPreset) : 'null'}, canvasSize: { width: _cs.width || 800, height: _cs.height || 600 } };
|
|
4529
4642
|
})();
|
|
4530
4643
|
`;
|
|
4531
4644
|
return code.trim();
|
|
@@ -4546,7 +4659,7 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
4546
4659
|
// anything runs; a direct caller gets the op's own failure instead.
|
|
4547
4660
|
const timelines = compileBatchTimelines(operations);
|
|
4548
4661
|
let code = `
|
|
4549
|
-
// Batch execute ${operations.length} operations
|
|
4662
|
+
// Batch execute ${oneLine(operations.length)} operations
|
|
4550
4663
|
(async function() {
|
|
4551
4664
|
// An op's own verdict. Deliberately narrow, matching the server-side rule:
|
|
4552
4665
|
// only an explicit success:false or ok:false is a failure, because most ops
|
|
@@ -4619,7 +4732,7 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
4619
4732
|
const opItemJs = typeof opItem === 'string' && /^\$\d+$/.test(opItem) ? `__refName(${opItem.slice(1)})` : JSON.stringify(opItem);
|
|
4620
4733
|
const opStep = typeof o._step === 'string' ? `op ${index}: ${o._step}` : `op ${index} (${op.type})`;
|
|
4621
4734
|
code += `
|
|
4622
|
-
// Operation ${index}: ${op.type}
|
|
4735
|
+
// Operation ${oneLine(index)}: ${oneLine(op.type)}
|
|
4623
4736
|
try {
|
|
4624
4737
|
const result${index} = await (async () => {
|
|
4625
4738
|
${opCode}
|
|
@@ -4803,14 +4916,55 @@ return { itemId: targetId, animationType: ${JSON.stringify(String(op.animationTy
|
|
|
4803
4916
|
return `
|
|
4804
4917
|
throw new Error(${JSON.stringify(String(op.relationType) + ': ' + fireIf)});
|
|
4805
4918
|
`;
|
|
4919
|
+
// No targetId is a relation on the item alone (the engine takes a null target, as staggered_with does); a
|
|
4920
|
+
// custom relation whose compute ignores the target needed a stand-in before.
|
|
4921
|
+
const noTarget = op.targetId === undefined || op.targetId === null || op.targetId === '';
|
|
4922
|
+
// A NAME PATTERN AND AN UPDATE (mcp-server#37). sourceId "dot-*" binds every item named like it, in name
|
|
4923
|
+
// order (dot-2 before dot-10), each with index and count added to its params, so "12 → 20 dots" is
|
|
4924
|
+
// adding members. update: true changes the params of the relation already bound instead of adding one.
|
|
4925
|
+
const glob = typeof op.sourceId === 'string' && op.sourceId.includes('*') && !op.sourceId.startsWith('$');
|
|
4926
|
+
if (glob || op.update === true) {
|
|
4927
|
+
const type = jsLiteral(String(op.relationType));
|
|
4928
|
+
const members = glob
|
|
4929
|
+
? `const __re = new RegExp(${jsLiteral('^' + String(op.sourceId).split('*').map((p) => p.replace(/[.+?^${}()|[\]\\]/g, '\\$&')).join('.*') + '$')});
|
|
4930
|
+
const __members = app.itemRegistry.getAll()
|
|
4931
|
+
.map((e) => ({ id: e.id || e.itemId, name: (e.item && ((e.item.data && e.item.data.name) || e.item.name)) || '' }))
|
|
4932
|
+
.filter((m) => __re.test(m.name))
|
|
4933
|
+
.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));
|
|
4934
|
+
if (!__members.length) throw new Error('no item is named like ' + ${jsLiteral(op.sourceId)} + ': name items with properties.name');`
|
|
4935
|
+
: `const __members = [{ id: ${srcRef} }];
|
|
4936
|
+
if (!app.getItemById(__members[0].id)) throw new Error('Source not found: ' + __members[0].id);`;
|
|
4937
|
+
return `
|
|
4938
|
+
${members}
|
|
4939
|
+
const targetId = ${noTarget ? 'null' : tgtRef};
|
|
4940
|
+
const target = ${noTarget ? 'null' : 'app.getItemById(targetId)'};
|
|
4941
|
+
${noTarget ? '' : "if (!target) throw new Error('Target not found: ' + targetId);\n"}${op.update === true ? "if (typeof app.modifyRelationParams !== 'function') throw new Error('this studio cannot change a bound relation (app.modifyRelationParams): update PinePaper Studio');\n" : ''}const __done = [], __failed = [];
|
|
4942
|
+
__members.forEach((m, i) => {
|
|
4943
|
+
const __p = Object.assign({}, ${relOpts}${glob ? ', { index: i, count: __members.length }' : ''});
|
|
4944
|
+
const ok = ${op.update === true
|
|
4945
|
+
? `app.modifyRelationParams(m.id, targetId, ${type}, __p) || (targetId === null && app.modifyRelationParams(m.id, undefined, ${type}, __p))`
|
|
4946
|
+
: glob
|
|
4947
|
+
// Already bound (a re-run after adding members): its params change, so index and count follow the new set.
|
|
4948
|
+
? `((app.relationRegistry && app.relationRegistry.getAssociations(m.id, ${type}).some((a) => a.target === targetId || (targetId === null && a.target == null)) && typeof app.modifyRelationParams === 'function')
|
|
4949
|
+
? (app.modifyRelationParams(m.id, targetId, ${type}, __p) || (targetId === null && app.modifyRelationParams(m.id, undefined, ${type}, __p)))
|
|
4950
|
+
: app.addRelation(app.getItemById(m.id), target, ${type}, __p))`
|
|
4951
|
+
: `app.addRelation(app.getItemById(m.id), target, ${type}, __p)`};
|
|
4952
|
+
(ok ? __done : __failed).push(m.id);
|
|
4953
|
+
});
|
|
4954
|
+
if (!__done.length) {
|
|
4955
|
+
const __why = ${op.update === true ? `'no ' + ${type} + ' relation is bound from ' + __failed.join(', ') + (targetId === null ? '' : ' to ' + targetId) + ' to change: bind it first (a relation op without update)'` : relationRefusalReasonJs(type)};
|
|
4956
|
+
return { success: false, relationType: ${type}, sourceIds: __failed, error: ${op.update === true ? '__why' : `'the studio refused the ' + ${type} + ' relation for ' + __failed.join(', ') + ' — nothing was bound' + (__why ? ': ' + __why : '.')`} };
|
|
4957
|
+
}
|
|
4958
|
+
return { success: true, relationType: ${type}, ${op.update === true ? 'updated' : 'bound'}: __done, targetId${glob ? ', count: __members.length' : ''}${(() => { const w = relationOptionWarning(op.relationType, op.relationOptions, ['index', 'count']); return w ? `, optionWarning: ${jsLiteral(w)}` : ''; })()}, ...(__failed.length ? { failed: __failed, warning: __failed.length + ' of ' + __members.length + ' were not ${op.update === true ? 'changed' : 'bound'}: ' + __failed.join(', ') } : {}) };
|
|
4959
|
+
`;
|
|
4960
|
+
}
|
|
4806
4961
|
return `
|
|
4807
4962
|
const sourceId = ${srcRef};
|
|
4808
|
-
const targetId = ${tgtRef};
|
|
4963
|
+
const targetId = ${noTarget ? 'null' : tgtRef};
|
|
4809
4964
|
const source = app.getItemById(sourceId);
|
|
4810
|
-
const target = app.getItemById(targetId);
|
|
4965
|
+
const target = ${noTarget ? 'null' : 'app.getItemById(targetId)'};
|
|
4811
4966
|
if (!source) throw new Error('Source not found: ' + sourceId);
|
|
4812
|
-
if (!target) throw new Error('Target not found: ' + targetId);
|
|
4813
|
-
const relationId = app.addRelation(source, target, ${JSON.stringify(String(op.relationType))}, ${relOpts});
|
|
4967
|
+
${noTarget ? '' : "if (!target) throw new Error('Target not found: ' + targetId);\n"}const relationId = app.addRelation(source, target, ${JSON.stringify(String(op.relationType))}, ${relOpts});
|
|
4814
4968
|
// NOT BOUND IS NOT A SUCCESS (owner rule 1, B119). addRelation answers false
|
|
4815
4969
|
// when it refuses — spring_follow "not loaded yet" — and this op returned
|
|
4816
4970
|
// { relationId: false } with nothing marking it failed, so the batch said
|
|
@@ -4820,7 +4974,7 @@ if (!relationId) {
|
|
|
4820
4974
|
return { success: false, relationId: false, sourceId, targetId, relationType: ${JSON.stringify(op.relationType)},
|
|
4821
4975
|
error: 'the studio refused the ' + ${JSON.stringify(op.relationType)} + ' relation between ' + sourceId + ' and ' + targetId + ' — nothing was bound' + (__why ? ': ' + __why : '.') };
|
|
4822
4976
|
}
|
|
4823
|
-
return { relationId, sourceId, targetId, relationType: ${JSON.stringify(String(op.relationType))} };
|
|
4977
|
+
return { relationId, sourceId, targetId, relationType: ${JSON.stringify(String(op.relationType))}${(() => { const w = relationOptionWarning(op.relationType, op.relationOptions); return w ? `, warning: ${jsLiteral(w)}` : ''; })()} };
|
|
4824
4978
|
`;
|
|
4825
4979
|
case 'delete':
|
|
4826
4980
|
const delRef = batchRef(op.itemId);
|
|
@@ -4876,7 +5030,7 @@ return { success: true, width: (_cs && _cs.width) || ${w}, height: (_cs && _cs.h
|
|
|
4876
5030
|
const foldTarget = mergePlan?.foldedInto.get(index);
|
|
4877
5031
|
if (foldTarget !== undefined) {
|
|
4878
5032
|
return `
|
|
4879
|
-
// Keyframes merged into operation ${foldTarget} (same target). A keyframe track
|
|
5033
|
+
// Keyframes merged into operation ${oneLine(foldTarget)} (same target). A keyframe track
|
|
4880
5034
|
// is replaced wholesale, so emitting both here would silently drop one.
|
|
4881
5035
|
return { itemId: ${batchRef(op.itemId)}, merged: true, mergedInto: ${foldTarget} };
|
|
4882
5036
|
`;
|
|
@@ -4906,7 +5060,7 @@ return { itemId: ${batchRef(op.itemId)}, merged: true, mergedInto: ${foldTarget}
|
|
|
4906
5060
|
}
|
|
4907
5061
|
const kfUnits = op.timeUnits;
|
|
4908
5062
|
if (kfUnits === 'seconds' || kfUnits === 'ms')
|
|
4909
|
-
kfExtra.push(`timeUnits:
|
|
5063
|
+
kfExtra.push(`timeUnits: ${jsLiteral(kfUnits)}`);
|
|
4910
5064
|
const kfExtraStr = kfExtra.length > 0 ? `, ${kfExtra.join(', ')}` : '';
|
|
4911
5065
|
const kfTextKeysJs = unanimatedTextKeysJs((kfSource || []).flatMap((k) => Object.keys(k.properties ?? {})));
|
|
4912
5066
|
const kfTextCheck = kfTextKeysJs ? `\nconst _kfIgnored = ${kfTextKeysJs};` : '';
|
|
@@ -4933,12 +5087,12 @@ const targetId = ${maskItemRef};
|
|
|
4933
5087
|
const item = app.getItemById(targetId);
|
|
4934
5088
|
if (!item) throw new Error('Item not found: ' + targetId);
|
|
4935
5089
|
if (!app.applyAnimatedMask) throw new Error('Mask system not available');
|
|
4936
|
-
const maskedGroup = app.applyAnimatedMask(item,
|
|
5090
|
+
const maskedGroup = app.applyAnimatedMask(item, ${jsLiteral(preset)}, ${maskOpts});
|
|
4937
5091
|
const maskRej = maskedGroup && maskedGroup.data && maskedGroup.data.maskAnimationRejected;
|
|
4938
5092
|
if (maskRej) {
|
|
4939
5093
|
return { success: false, itemId: targetId, error: 'Masked, but animation preset "' + maskRej.requested + '" is unknown — it will NOT animate.', requested: maskRej.requested, known: maskRej.known };
|
|
4940
5094
|
}
|
|
4941
|
-
return { success: !!maskedGroup, itemId: targetId, preset:
|
|
5095
|
+
return { success: !!maskedGroup, itemId: targetId, preset: ${jsLiteral(preset)} };
|
|
4942
5096
|
`;
|
|
4943
5097
|
}
|
|
4944
5098
|
case 'apply_effect': {
|
|
@@ -4952,12 +5106,12 @@ if (!item) throw new Error('Item not found: ' + targetId);
|
|
|
4952
5106
|
// shape, so an effect the engine refused — an unknown preset, a facade that
|
|
4953
5107
|
// is not loaded — was recorded as applied. The batch cannot report what the
|
|
4954
5108
|
// op never tells it.
|
|
4955
|
-
const _eff = app.applyEffect(item,
|
|
5109
|
+
const _eff = app.applyEffect(item, ${jsLiteral(op.effectType || 'sparkle')}, ${effParams});
|
|
4956
5110
|
if (_eff && (_eff.success === false || _eff.ok === false)) {
|
|
4957
|
-
return { success: false, itemId: targetId, effectType:
|
|
5111
|
+
return { success: false, itemId: targetId, effectType: ${jsLiteral(op.effectType || 'sparkle')},
|
|
4958
5112
|
error: _eff.error || _eff.reason || 'the effect was refused without a reason' };
|
|
4959
5113
|
}
|
|
4960
|
-
return { itemId: targetId, effectType:
|
|
5114
|
+
return { itemId: targetId, effectType: ${jsLiteral(op.effectType || 'sparkle')} };
|
|
4961
5115
|
`;
|
|
4962
5116
|
}
|
|
4963
5117
|
case 'play_timeline': {
|
|
@@ -5005,7 +5159,7 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
5005
5159
|
if (typeof length === 'number')
|
|
5006
5160
|
range.length = length;
|
|
5007
5161
|
return `
|
|
5008
|
-
// Read export chunk at ${offset}
|
|
5162
|
+
// Read export chunk at ${oneLine(offset)}
|
|
5009
5163
|
(async function() {
|
|
5010
5164
|
if (!app.exportEngine || typeof app.exportEngine.readExport !== 'function') {
|
|
5011
5165
|
return { ok: false, reason: 'app.exportEngine.readExport unavailable — update FxTool' };
|
|
@@ -5050,9 +5204,13 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
5050
5204
|
}
|
|
5051
5205
|
generateAgentExport(input) {
|
|
5052
5206
|
const validated = AgentExportInputSchema.parse(input);
|
|
5053
|
-
const { platform, format, quality, framing, duration, estimateOnly, scale, fps, pdf: pdfOpts, region, time: stillTime, maxBytes, loop: gifLoop, broadcast, broadcastHeadroom, bitrate, minBitrate, bitrateMode, transparent, alphaQuantizer, deterministic, motionBlur, ad: adOpts, frames } = validated;
|
|
5207
|
+
const { platform, format, quality, framing, duration, estimateOnly, scale, fps, pdf: pdfOpts, region, time: stillTime, maxBytes, loop: gifLoop, broadcast, broadcastHeadroom, bitrate, minBitrate, bitrateMode, transparent, alphaQuantizer, deterministic, motionBlur, ad: adOpts, frames, startTime } = validated;
|
|
5054
5208
|
// Encoder options (FxTool 16719759); the schema keeps them to mp4 / webm.
|
|
5055
5209
|
const videoEncodeOpts = {};
|
|
5210
|
+
// A RANGE of the timeline (FxTool #250): frames for t in [startTime, startTime + duration), each exactly the frame
|
|
5211
|
+
// a full export has at that time (the engine renders the frames before startTime without encoding them).
|
|
5212
|
+
if (startTime !== undefined)
|
|
5213
|
+
videoEncodeOpts.startTime = startTime;
|
|
5056
5214
|
if (broadcast)
|
|
5057
5215
|
videoEncodeOpts.broadcast = true;
|
|
5058
5216
|
// Headroom defaults to 12 with broadcast (retest of caa9560): on a
|
|
@@ -5172,10 +5330,10 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
5172
5330
|
if (exportFormat === 'srt' || exportFormat === 'vtt' || exportFormat === 'scc') {
|
|
5173
5331
|
const fmt = exportFormat;
|
|
5174
5332
|
return `
|
|
5175
|
-
// Export: captions (${fmt}) from text items with a lifetime
|
|
5333
|
+
// Export: captions (${oneLine(fmt)}) from text items with a lifetime
|
|
5176
5334
|
(function() {
|
|
5177
5335
|
if (!app.itemRegistry || typeof app.itemRegistry.getAll !== 'function') {
|
|
5178
|
-
return { success: false, format:
|
|
5336
|
+
return { success: false, format: ${jsLiteral(fmt)}, error: 'this studio cannot list items (app.itemRegistry.getAll) — update PinePaper Studio.' };
|
|
5179
5337
|
}
|
|
5180
5338
|
const texts = app.itemRegistry.getAll().filter(function(e) {
|
|
5181
5339
|
const it = e && e.item;
|
|
@@ -5183,7 +5341,7 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
5183
5341
|
});
|
|
5184
5342
|
const timed = texts.filter(function(e) { return e.item.data && typeof e.item.data.bornAt === 'number'; });
|
|
5185
5343
|
if (!timed.length) {
|
|
5186
|
-
return { success: false, format:
|
|
5344
|
+
return { success: false, format: ${jsLiteral(fmt)}, error: 'no captions to export: no text item has a lifetime. Stage each caption with properties.bornAt / ttl (seconds) on create_item or modify_item, then export ${fmt}.', untimedText: texts.length };
|
|
5187
5345
|
}
|
|
5188
5346
|
const end = ${videoDuration};
|
|
5189
5347
|
const ts = function(t, sep) {
|
|
@@ -5203,7 +5361,7 @@ ${fmt === 'scc' ? ` return { success: true, format: 'scc', sccCues: cues, cues:
|
|
|
5203
5361
|
return ${fmt === 'srt' ? `(i + 1) + '\\n' + ts(c.start, ',') + ' --> ' + ts(c.stop, ',')` : `ts(c.start, '.') + ' --> ' + ts(c.stop, '.')`} + '\\n' + c.text;
|
|
5204
5362
|
}).join('\\n\\n') + '\\n';
|
|
5205
5363
|
const text = ${fmt === 'vtt' ? `'WEBVTT\\n\\n' + body` : 'body'};
|
|
5206
|
-
return { success: true, format:
|
|
5364
|
+
return { success: true, format: ${jsLiteral(fmt)}, data: text, mimeType: ${jsLiteral(fmt === 'srt' ? 'application/x-subrip' : 'text/vtt')}, size: text.length, cues: cues.length, untimedText: texts.length - timed.length };
|
|
5207
5365
|
})();`.trim();
|
|
5208
5366
|
}
|
|
5209
5367
|
if (exportFormat === 'wav') {
|
|
@@ -5253,7 +5411,7 @@ ${fmt === 'scc' ? ` return { success: true, format: 'scc', sccCues: cues, cues:
|
|
|
5253
5411
|
bin += String.fromCharCode.apply(null, bytes.subarray(i, i + 8192));
|
|
5254
5412
|
}
|
|
5255
5413
|
return {
|
|
5256
|
-
success: true, format: 'wav', platform:
|
|
5414
|
+
success: true, format: 'wav', platform: ${jsLiteral(platform)},
|
|
5257
5415
|
data: 'data:audio/wav;base64,' + btoa(bin),
|
|
5258
5416
|
size: bytes.length,
|
|
5259
5417
|
duration: r.duration, sampleRate: r.sampleRate, sounds: r.sounds, placed: r.placed,
|
|
@@ -5264,12 +5422,14 @@ ${fmt === 'scc' ? ` return { success: true, format: 'scc', sccCues: cues, cues:
|
|
|
5264
5422
|
})();`.trim();
|
|
5265
5423
|
}
|
|
5266
5424
|
return `
|
|
5267
|
-
// Smart export for ${platform}
|
|
5425
|
+
// Smart export for ${oneLine(platform)}
|
|
5268
5426
|
(async function() {${ENSURE_EXPORT_ENGINE}
|
|
5269
|
-
const platform =
|
|
5270
|
-
const format =
|
|
5271
|
-
|
|
5272
|
-
|
|
5427
|
+
const platform = ${jsLiteral(platform)};
|
|
5428
|
+
const format = ${jsLiteral(exportFormat)};${startTime !== undefined ? `
|
|
5429
|
+
if (format !== 'mp4' && format !== 'webm') return { success: false, format, error: 'startTime exports a range of the timeline, which only mp4 and webm can: ' + format + ' would start from 0.' };
|
|
5430
|
+
if (!(app.exportEngine && app.exportEngine.supportsTimeRange === true)) return { success: false, format, error: 'this studio cannot export a range of the timeline (startTime): update PinePaper Studio, or export from 0.' };` : ''}
|
|
5431
|
+
const quality = ${jsLiteral(qualityLevel)};
|
|
5432
|
+
const framing = ${jsLiteral(framing)};
|
|
5273
5433
|
// An explicit fps wins over the tier's. The tiers bundle frame rate with
|
|
5274
5434
|
// compression (draft 15 / standard 30 / high 60), so "higher quality" also
|
|
5275
5435
|
// doubled the frame count and the render time — a surprise the schema used
|
|
@@ -6438,7 +6598,7 @@ ${(() => {
|
|
|
6438
6598
|
if (__bs.stale) {
|
|
6439
6599
|
result.stale = true;
|
|
6440
6600
|
result.fidelity = result.fidelity || { warnings: [] };
|
|
6441
|
-
result.fidelity.warnings = (result.fidelity.warnings || []).concat([{ code: 'stale_build', message: 'this studio is running an old build (' + __bs.buildId + '; the live build is ' + __bs.liveBuildId + '): this file was made by the old code
|
|
6601
|
+
result.fidelity.warnings = (result.fidelity.warnings || []).concat([{ code: 'stale_build', message: 'this studio is running an old build (' + __bs.buildId + '; the live build is ' + __bs.liveBuildId + '): this file was made by the old code, so it may differ from what the live build makes. pinepaper_clear_canvas (or pinepaper_agent_start_job) loads the live build; it empties the canvas, so rebuild the scene and export again if the difference matters.' }]);
|
|
6442
6602
|
}
|
|
6443
6603
|
}
|
|
6444
6604
|
} catch (_) { /* older studio */ }
|
|
@@ -6450,7 +6610,7 @@ ${(() => {
|
|
|
6450
6610
|
if (__rl && __rl.enabled) {
|
|
6451
6611
|
result.fidelity = result.fidelity || { warnings: [] };
|
|
6452
6612
|
if (['png', 'jpg', 'webp', 'pdf', 'mp4', 'webm'].indexOf(${JSON.stringify(format)}) === -1) {
|
|
6453
|
-
result.fidelity.warnings = (result.fidelity.warnings || []).concat([{ code: 'relight_not_applied', message: 'a relight rig is set, but ${format} is not relit. Export png, pdf, mp4 or webm to keep the lighting.' }]);
|
|
6613
|
+
result.fidelity.warnings = (result.fidelity.warnings || []).concat([{ code: 'relight_not_applied', message: 'a relight rig is set, but ' + ${jsLiteral(format)} + ' is not relit. Export png, pdf, mp4 or webm to keep the lighting.' }]);
|
|
6454
6614
|
} else {
|
|
6455
6615
|
result.relight = { applied: true, lights: (__rl.lights || []).length };
|
|
6456
6616
|
}
|
|
@@ -6681,7 +6841,7 @@ ${(() => {
|
|
|
6681
6841
|
const validated = AnimateLetterCollageInputSchema.parse(input);
|
|
6682
6842
|
const { collageId, animationType, staggerDelay = 0.1, animationSpeed = 1, } = validated;
|
|
6683
6843
|
return `
|
|
6684
|
-
// Animate letter collage: ${collageId}
|
|
6844
|
+
// Animate letter collage: ${oneLine(collageId)}
|
|
6685
6845
|
(function() {
|
|
6686
6846
|
const collageId = ${JSON.stringify(collageId)};
|
|
6687
6847
|
const animationType = ${JSON.stringify(animationType)};
|
|
@@ -6778,10 +6938,10 @@ ${(() => {
|
|
|
6778
6938
|
}
|
|
6779
6939
|
|
|
6780
6940
|
try {
|
|
6781
|
-
const result = await app.mapSystem.loadMap(
|
|
6941
|
+
const result = await app.mapSystem.loadMap(${jsLiteral(mapId)}, ${optionsStr});
|
|
6782
6942
|
return {
|
|
6783
6943
|
success: true,
|
|
6784
|
-
mapId: result.mapId ||
|
|
6944
|
+
mapId: result.mapId || ${jsLiteral(mapId)},
|
|
6785
6945
|
// regionPaths is a Map: .length was undefined, so every load reported
|
|
6786
6946
|
// regions: 0 (gate D3). size, with the feature count as a fallback.
|
|
6787
6947
|
regions: (result.regions && (result.regions.size ?? result.regions.length)) || (result.geoData && result.geoData.features ? result.geoData.features.length : 0),
|
|
@@ -7198,7 +7358,7 @@ ${(() => {
|
|
|
7198
7358
|
}
|
|
7199
7359
|
|
|
7200
7360
|
try {
|
|
7201
|
-
const result = await app.mapSystem.importCustomMap(
|
|
7361
|
+
const result = await app.mapSystem.importCustomMap(${jsLiteral(validated.url)}, ${optionsStr});
|
|
7202
7362
|
// A summary, not the engine's return: that carries the Paper group and
|
|
7203
7363
|
// the region Map, which serialised into a raw Paper.js dump (gate N1).
|
|
7204
7364
|
const __r = result && result.regions;
|
|
@@ -7316,7 +7476,7 @@ ${(() => {
|
|
|
7316
7476
|
loop: ${validated.loop !== false},
|
|
7317
7477
|
colors: ${colorsStr},${validated.waveSpeed !== undefined ? `
|
|
7318
7478
|
waveSpeed: ${validated.waveSpeed},` : ''}
|
|
7319
|
-
waveDirection:
|
|
7479
|
+
waveDirection: ${jsLiteral(validated.waveDirection || 'horizontal')}
|
|
7320
7480
|
});
|
|
7321
7481
|
return { success: true, animatedRegions: result?.animatedRegions || [], totalRegions: result?.totalRegions || 0 };
|
|
7322
7482
|
} catch (error) {
|
|
@@ -7388,7 +7548,7 @@ ${(() => {
|
|
|
7388
7548
|
includeSelected: ${validated.includeSelected !== false},
|
|
7389
7549
|
includeColors: ${validated.includeColors !== false},
|
|
7390
7550
|
download: ${validated.download === true},
|
|
7391
|
-
filename:
|
|
7551
|
+
filename: ${jsLiteral(validated.filename || 'map-regions.csv')}
|
|
7392
7552
|
});
|
|
7393
7553
|
return { success: true, csv: result?.csv || '', regionCount: result?.regionCount || 0 };
|
|
7394
7554
|
} catch (error) {
|
|
@@ -7645,7 +7805,7 @@ ${(() => {
|
|
|
7645
7805
|
|
|
7646
7806
|
const item = (app.getItemById && app.getItemById(${JSON.stringify(itemId)})) || (app.itemRegistry && app.itemRegistry.get && app.itemRegistry.get(${JSON.stringify(itemId)}) && (app.itemRegistry.get(${JSON.stringify(itemId)}).item || app.itemRegistry.get(${JSON.stringify(itemId)})));
|
|
7647
7807
|
if (!item) {
|
|
7648
|
-
return { success: false, error: 'Item ${itemId} not found' };
|
|
7808
|
+
return { success: false, error: 'Item ' + ${jsLiteral(itemId)} + ' not found' };
|
|
7649
7809
|
}
|
|
7650
7810
|
|
|
7651
7811
|
try {
|
|
@@ -7674,7 +7834,7 @@ ${(() => {
|
|
|
7674
7834
|
|
|
7675
7835
|
const item = (app.getItemById && app.getItemById(${JSON.stringify(itemId)})) || (app.itemRegistry && app.itemRegistry.get && app.itemRegistry.get(${JSON.stringify(itemId)}) && (app.itemRegistry.get(${JSON.stringify(itemId)}).item || app.itemRegistry.get(${JSON.stringify(itemId)})));
|
|
7676
7836
|
if (!item) {
|
|
7677
|
-
return { success: false, error: 'Item ${itemId} not found' };
|
|
7837
|
+
return { success: false, error: 'Item ' + ${jsLiteral(itemId)} + ' not found' };
|
|
7678
7838
|
}
|
|
7679
7839
|
|
|
7680
7840
|
try {
|
|
@@ -8047,10 +8207,10 @@ ${(() => {
|
|
|
8047
8207
|
const paramsStr = validated.params ? JSON.stringify(validated.params) : '{}';
|
|
8048
8208
|
const templatesStr = validated.templates ? JSON.stringify(validated.templates) : '[]';
|
|
8049
8209
|
return `
|
|
8050
|
-
// Register custom relation: ${validated.name}
|
|
8210
|
+
// Register custom relation: ${oneLine(validated.name)}
|
|
8051
8211
|
(function() {
|
|
8052
8212
|
try {
|
|
8053
|
-
app.registerRelationRule(
|
|
8213
|
+
app.registerRelationRule(${jsLiteral(validated.name)}, {
|
|
8054
8214
|
description: ${JSON.stringify(validated.description || '')},
|
|
8055
8215
|
params: ${paramsStr},
|
|
8056
8216
|
compute: (ctx) => {
|
|
@@ -8064,7 +8224,11 @@ ${(() => {
|
|
|
8064
8224
|
continuous: ${validated.continuous !== false},
|
|
8065
8225
|
priority: ${validated.priority || 0}
|
|
8066
8226
|
});
|
|
8067
|
-
|
|
8227
|
+
// The definition as given, kept on the page so a later call can read it back (pinepaper_lint_scene
|
|
8228
|
+
// customRelations): the engine keeps the compiled rule, not the text an agent would edit.
|
|
8229
|
+
window.__ppCustomRelations = window.__ppCustomRelations || {};
|
|
8230
|
+
window.__ppCustomRelations[${jsLiteral(validated.name)}] = ${jsLiteral({ name: validated.name, ...(validated.description ? { description: validated.description } : {}), params: validated.params ?? {}, computeFunction: validated.computeFunction, applyFunction: validated.applyFunction })};
|
|
8231
|
+
return { success: true, relationName: ${jsLiteral(validated.name)}, message: 'Custom relation registered successfully' };
|
|
8068
8232
|
} catch (error) {
|
|
8069
8233
|
return { success: false, error: error.message };
|
|
8070
8234
|
}
|
|
@@ -8095,7 +8259,7 @@ ${(() => {
|
|
|
8095
8259
|
// __ppValue with the scene lint beside it, and a throw comes back as { step, itemId, reason }, never a bare
|
|
8096
8260
|
// message (and never "undefined" for a thrown non-Error).
|
|
8097
8261
|
return `
|
|
8098
|
-
// ${description}
|
|
8262
|
+
// ${oneLine(description)}
|
|
8099
8263
|
(async function() {
|
|
8100
8264
|
// THE SCENE LINT AFTER THE CODE (FxTool lintScene().warnings): what the scene will show wrong without anything
|
|
8101
8265
|
// failing (a centred title that runs out of the frame). Carried out under __ppLint; the server moves it beside the
|
|
@@ -8151,21 +8315,21 @@ ${(() => {
|
|
|
8151
8315
|
// Per-preset options. Custom keyframes are an animated-mask extension —
|
|
8152
8316
|
// when supplied, fall through to maskingSystem.applyCustomMask instead.
|
|
8153
8317
|
return `
|
|
8154
|
-
// Apply animated mask to ${validated.itemId}
|
|
8318
|
+
// Apply animated mask to ${oneLine(validated.itemId)}
|
|
8155
8319
|
(function() {
|
|
8156
8320
|
if (!app.applyAnimatedMask) {
|
|
8157
8321
|
return { success: false, error: 'Mask system not available' };
|
|
8158
8322
|
}
|
|
8159
|
-
const item = app.getItemById(
|
|
8160
|
-
if (!item) return { success: false, error: 'Item not found: ${validated.itemId}
|
|
8323
|
+
const item = app.getItemById(${jsLiteral(validated.itemId)});
|
|
8324
|
+
if (!item) return { success: false, error: 'Item not found: ' + ${jsLiteral(validated.itemId)} };
|
|
8161
8325
|
|
|
8162
8326
|
try {
|
|
8163
8327
|
const mergedOpts = Object.assign({}, ${optionsStr}, ${maskOptionsStr});
|
|
8164
8328
|
let maskedGroup;
|
|
8165
8329
|
if (${keyframesStr} && app.maskingSystem && app.maskingSystem.applyCustomMask) {
|
|
8166
|
-
maskedGroup = app.maskingSystem.applyCustomMask(item,
|
|
8330
|
+
maskedGroup = app.maskingSystem.applyCustomMask(item, ${jsLiteral(customShape)}, ${keyframesStr}, mergedOpts);
|
|
8167
8331
|
} else {
|
|
8168
|
-
maskedGroup = app.applyAnimatedMask(item,
|
|
8332
|
+
maskedGroup = app.applyAnimatedMask(item, ${jsLiteral(preset)}, mergedOpts);
|
|
8169
8333
|
}
|
|
8170
8334
|
// A mask with an unknown ANIMATION preset still masks — the item just never
|
|
8171
8335
|
// animates — so !!maskedGroup is true either way. The engine records which.
|
|
@@ -8173,13 +8337,13 @@ ${(() => {
|
|
|
8173
8337
|
if (rej) {
|
|
8174
8338
|
return {
|
|
8175
8339
|
success: false,
|
|
8176
|
-
itemId:
|
|
8340
|
+
itemId: ${jsLiteral(validated.itemId)},
|
|
8177
8341
|
error: 'Masked, but the animation preset "' + rej.requested + '" is unknown — it will NOT animate.',
|
|
8178
8342
|
requested: rej.requested,
|
|
8179
8343
|
known: rej.known,
|
|
8180
8344
|
};
|
|
8181
8345
|
}
|
|
8182
|
-
return { success: !!maskedGroup, itemId:
|
|
8346
|
+
return { success: !!maskedGroup, itemId: ${jsLiteral(validated.itemId)}, preset: ${jsLiteral(preset)} };
|
|
8183
8347
|
} catch (error) {
|
|
8184
8348
|
return { success: false, error: error.message };
|
|
8185
8349
|
}
|
|
@@ -8191,17 +8355,17 @@ ${(() => {
|
|
|
8191
8355
|
const keyframesStr = JSON.stringify(validated.keyframes);
|
|
8192
8356
|
const maskOptionsStr = validated.maskOptions ? JSON.stringify(validated.maskOptions) : '{}';
|
|
8193
8357
|
return `
|
|
8194
|
-
// Apply custom mask to ${validated.itemId}
|
|
8358
|
+
// Apply custom mask to ${oneLine(validated.itemId)}
|
|
8195
8359
|
(function() {
|
|
8196
8360
|
if (!app.maskingSystem || !app.maskingSystem.applyCustomMask) {
|
|
8197
8361
|
return { success: false, error: 'Mask system not available' };
|
|
8198
8362
|
}
|
|
8199
|
-
const item = app.getItemById(
|
|
8200
|
-
if (!item) return { success: false, error: 'Item not found: ${validated.itemId}
|
|
8363
|
+
const item = app.getItemById(${jsLiteral(validated.itemId)});
|
|
8364
|
+
if (!item) return { success: false, error: 'Item not found: ' + ${jsLiteral(validated.itemId)} };
|
|
8201
8365
|
|
|
8202
8366
|
try {
|
|
8203
|
-
const maskedGroup = app.maskingSystem.applyCustomMask(item,
|
|
8204
|
-
return { success: !!maskedGroup, itemId:
|
|
8367
|
+
const maskedGroup = app.maskingSystem.applyCustomMask(item, ${jsLiteral(validated.maskType)}, ${keyframesStr}, ${maskOptionsStr});
|
|
8368
|
+
return { success: !!maskedGroup, itemId: ${jsLiteral(validated.itemId)}, maskType: ${jsLiteral(validated.maskType)} };
|
|
8205
8369
|
} catch (error) {
|
|
8206
8370
|
return { success: false, error: error.message };
|
|
8207
8371
|
}
|
|
@@ -8211,20 +8375,20 @@ ${(() => {
|
|
|
8211
8375
|
generateRemoveMask(input) {
|
|
8212
8376
|
const validated = RemoveMaskInputSchema.parse(input);
|
|
8213
8377
|
return `
|
|
8214
|
-
// Remove mask from ${validated.itemId}
|
|
8378
|
+
// Remove mask from ${oneLine(validated.itemId)}
|
|
8215
8379
|
(function() {
|
|
8216
8380
|
if (!app.removeMask) {
|
|
8217
8381
|
return { success: false, error: 'Mask system not available' };
|
|
8218
8382
|
}
|
|
8219
8383
|
// The item id may point at either the masked group OR the original item;
|
|
8220
8384
|
// app.removeMask expects the masked group, so resolve and walk up if needed.
|
|
8221
|
-
const item = app.getItemById(
|
|
8222
|
-
if (!item) return { success: false, error: 'Item not found: ${validated.itemId}
|
|
8385
|
+
const item = app.getItemById(${jsLiteral(validated.itemId)});
|
|
8386
|
+
if (!item) return { success: false, error: 'Item not found: ' + ${jsLiteral(validated.itemId)} };
|
|
8223
8387
|
const maskedGroup = (item.data && item.data.isMaskedGroup) ? item : (item.parent && item.parent.data && item.parent.data.isMaskedGroup ? item.parent : item);
|
|
8224
8388
|
|
|
8225
8389
|
try {
|
|
8226
8390
|
app.removeMask(maskedGroup);
|
|
8227
|
-
return { success: true, itemId:
|
|
8391
|
+
return { success: true, itemId: ${jsLiteral(validated.itemId)}, message: 'Mask removed' };
|
|
8228
8392
|
} catch (error) {
|
|
8229
8393
|
return { success: false, error: error.message };
|
|
8230
8394
|
}
|
|
@@ -8253,7 +8417,7 @@ ${(() => {
|
|
|
8253
8417
|
return { success: false, error: 'app.listAnimatableProperties unavailable — update the studio. The keyframe tool lists the properties it accepts in its own description meanwhile.' };
|
|
8254
8418
|
}
|
|
8255
8419
|
const item = app.getItemById(${JSON.stringify(itemId)});
|
|
8256
|
-
if (!item) { return { success: false, error: 'no item ${itemId}
|
|
8420
|
+
if (!item) { return { success: false, error: 'no item ' + ${jsLiteral(itemId)} }; }
|
|
8257
8421
|
const r = app.listAnimatableProperties(item);
|
|
8258
8422
|
return { success: true, ...r };
|
|
8259
8423
|
})();`.trim();
|
|
@@ -8351,7 +8515,7 @@ ${(() => {
|
|
|
8351
8515
|
generateApplyTemplate(input) {
|
|
8352
8516
|
const { templateId, category, listOnly } = input;
|
|
8353
8517
|
if (listOnly || !templateId) {
|
|
8354
|
-
const categoryFilter = category ?
|
|
8518
|
+
const categoryFilter = category ? jsLiteral(category) : 'null';
|
|
8355
8519
|
return `
|
|
8356
8520
|
// List available templates
|
|
8357
8521
|
(async function() {
|
|
@@ -8379,7 +8543,7 @@ ${(() => {
|
|
|
8379
8543
|
`.trim();
|
|
8380
8544
|
}
|
|
8381
8545
|
return `
|
|
8382
|
-
// Apply template: ${templateId}
|
|
8546
|
+
// Apply template: ${oneLine(templateId)}
|
|
8383
8547
|
(async function() {
|
|
8384
8548
|
if (!app.templateManager) {
|
|
8385
8549
|
return { error: 'Template manager not available. Make sure PinePaper Studio is loaded.' };
|
|
@@ -8426,7 +8590,7 @@ ${(() => {
|
|
|
8426
8590
|
let fidelity = {};
|
|
8427
8591
|
try {
|
|
8428
8592
|
const tpl = (app.templateManager.getAllTemplates() || [])
|
|
8429
|
-
.find(function(t) { return t && (t.id ===
|
|
8593
|
+
.find(function(t) { return t && (t.id === ${jsLiteral(templateId)} || t.templateId === ${jsLiteral(templateId)}); });
|
|
8430
8594
|
const items = tpl && tpl.data && Array.isArray(tpl.data.items) ? tpl.data.items : null;
|
|
8431
8595
|
const declared = items ? items.filter(function(i) { return i && i.type === 'group' && i.paperJSON; }).length : 0;
|
|
8432
8596
|
if (declared > 0 && app.itemRegistry && typeof app.itemRegistry.getAll === 'function') {
|
|
@@ -8447,7 +8611,7 @@ ${(() => {
|
|
|
8447
8611
|
}
|
|
8448
8612
|
} catch (e) { /* a check that cannot run must not fail the load */ }
|
|
8449
8613
|
|
|
8450
|
-
return { success: true, templateId:
|
|
8614
|
+
return { success: true, templateId: ${jsLiteral(templateId)}, message: 'Template applied successfully. Canvas has been replaced with template content.', ...fidelity };
|
|
8451
8615
|
} catch (e) {
|
|
8452
8616
|
return { error: 'Failed to apply template: ' + e.message };
|
|
8453
8617
|
}
|
|
@@ -8497,7 +8661,7 @@ ${(() => {
|
|
|
8497
8661
|
// The bytes then go in as a DATA URL, which img.src loads without touching
|
|
8498
8662
|
// the network again — so a host that allows fetch but blocks the image
|
|
8499
8663
|
// load (CORS on <img>, a tainted-canvas refusal) now works too.
|
|
8500
|
-
let src =
|
|
8664
|
+
let src = ${jsLiteral(url)};
|
|
8501
8665
|
// A STAGED KEY, not the bytes. The handler writes anything over ~64KB to
|
|
8502
8666
|
// window.__ppStage and passes the key instead, because the governor's
|
|
8503
8667
|
// loop-guard transform bails on a code string that large and takes the
|
|
@@ -8559,7 +8723,7 @@ ${mask ? ` // A MASK REPLACES THE RASTER (round 8 DD, 4.10). applyMask builds
|
|
|
8559
8723
|
// refused instead of answered with the raster's bounds.
|
|
8560
8724
|
const masked = app.imageTools.applyMask(raster, ${JSON.stringify(mask)});
|
|
8561
8725
|
if (!masked) {
|
|
8562
|
-
return { success: false, itemId: itemId, error: 'the ${mask} mask was not applied — the image may not be a raster yet. The unmasked image is on the canvas as ' + itemId + '.' };
|
|
8726
|
+
return { success: false, itemId: itemId, error: 'the ' + ${jsLiteral(mask)} + ' mask was not applied — the image may not be a raster yet. The unmasked image is on the canvas as ' + itemId + '.' };
|
|
8563
8727
|
}
|
|
8564
8728
|
if (app.itemRegistry && typeof app.itemRegistry.rebind === 'function') app.itemRegistry.rebind(itemId, masked);
|
|
8565
8729
|
placed = masked;
|
|
@@ -8586,7 +8750,7 @@ ${mask ? ` // A MASK REPLACES THE RASTER (round 8 DD, 4.10). applyMask builds
|
|
|
8586
8750
|
success: true,
|
|
8587
8751
|
itemId: itemId,
|
|
8588
8752
|
message: 'Image imported and placed on canvas.',${name ? `\n name: ${JSON.stringify(name)},` : ''}
|
|
8589
|
-
bounds: { x: b.x, y: b.y, width: b.width, height: b.height }${mask ? `,\n mask:
|
|
8753
|
+
bounds: { x: b.x, y: b.y, width: b.width, height: b.height }${mask ? `,\n mask: ${jsLiteral(mask)}` : ''}${hasShadow ? ',\n shadow: __shadow' : ''},
|
|
8590
8754
|
// Stored smaller than its source (the studio's cap): said, with the
|
|
8591
8755
|
// way to keep every pixel. The studio reports native size where it can.
|
|
8592
8756
|
...((entry && entry.nativeWidth && entry.width && entry.nativeWidth > entry.width)
|
|
@@ -8680,7 +8844,7 @@ ${mask ? ` // A MASK REPLACES THE RASTER (round 8 DD, 4.10). applyMask builds
|
|
|
8680
8844
|
(function() {
|
|
8681
8845
|
${guard}
|
|
8682
8846
|
const scene = app.sceneManager.getScene(${JSON.stringify(input.sceneId)});
|
|
8683
|
-
if (!scene) return { error: 'Scene not found: ${input.sceneId}
|
|
8847
|
+
if (!scene) return { error: 'Scene not found: ' + ${jsLiteral(input.sceneId)} };
|
|
8684
8848
|
return { success: true, action: 'info', scene: scene };
|
|
8685
8849
|
})();
|
|
8686
8850
|
`.trim();
|
|
@@ -9144,7 +9308,7 @@ ${input.cutSound !== undefined ? ` const __cuts = (__cr && __cr.cuts) || (app.s
|
|
|
9144
9308
|
selected.push(id);
|
|
9145
9309
|
}
|
|
9146
9310
|
}
|
|
9147
|
-
return { success: true, action: 'select', mode:
|
|
9311
|
+
return { success: true, action: 'select', mode: ${jsLiteral(mode)}, selected };
|
|
9148
9312
|
})();`.trim();
|
|
9149
9313
|
}
|
|
9150
9314
|
case 'select_all':
|
|
@@ -9201,7 +9365,7 @@ ${input.cutSound !== undefined ? ` const __cuts = (__cr && __cr.cuts) || (app.s
|
|
|
9201
9365
|
*/
|
|
9202
9366
|
_facadeCall(method, args, label) {
|
|
9203
9367
|
return `
|
|
9204
|
-
// ${label}
|
|
9368
|
+
// ${oneLine(label)}
|
|
9205
9369
|
(async function() {
|
|
9206
9370
|
if (typeof app.${method} !== 'function') {
|
|
9207
9371
|
return { success: false, error: 'app.${method}() unavailable — update PinePaper Studio to a build that has it.' };
|
|
@@ -9223,7 +9387,7 @@ ${input.cutSound !== undefined ? ` const __cuts = (__cr && __cr.cuts) || (app.s
|
|
|
9223
9387
|
// studios, and a brand must not cross between them.
|
|
9224
9388
|
if (input.action === 'get' || input.action === 'clear') {
|
|
9225
9389
|
return `
|
|
9226
|
-
// Brand kit: ${input.action} the active brand
|
|
9390
|
+
// Brand kit: ${oneLine(input.action)} the active brand
|
|
9227
9391
|
(function() {
|
|
9228
9392
|
const b = (typeof window !== 'undefined' && window.__ppActiveBrand) || null;
|
|
9229
9393
|
${input.action === 'clear' ? 'if (typeof window !== "undefined") window.__ppActiveBrand = null;' : ''}
|
|
@@ -9480,7 +9644,7 @@ ${loads}
|
|
|
9480
9644
|
__src = raster.url;
|
|
9481
9645
|
}`;
|
|
9482
9646
|
const call = (method, args, label) => `
|
|
9483
|
-
// ${label}
|
|
9647
|
+
// ${oneLine(label)}
|
|
9484
9648
|
(async function() {
|
|
9485
9649
|
if (typeof app.${method} !== 'function') {
|
|
9486
9650
|
return { success: false, error: 'app.${method}() unavailable — update PinePaper Studio to a build that has it.' };
|
|
@@ -9624,7 +9788,7 @@ ${resolveSource}
|
|
|
9624
9788
|
case 'fit': {
|
|
9625
9789
|
const mode = input.mode || 'contain';
|
|
9626
9790
|
return `
|
|
9627
|
-
// Fit item to the export frame (${mode})
|
|
9791
|
+
// Fit item to the export frame (${oneLine(mode)})
|
|
9628
9792
|
(function() {
|
|
9629
9793
|
// Route through the agent facade, not app.fitToFrame directly: an agent that
|
|
9630
9794
|
// just called upload_video holds a MEDIA id (vraster_…), which is a different
|
|
@@ -9667,7 +9831,7 @@ ${resolveSource}
|
|
|
9667
9831
|
case 'flip': {
|
|
9668
9832
|
const dir = input.direction || 'horizontal';
|
|
9669
9833
|
return `
|
|
9670
|
-
// Flip item ${dir}ly
|
|
9834
|
+
// Flip item ${oneLine(dir)}ly
|
|
9671
9835
|
(function() {
|
|
9672
9836
|
const entry = app.itemRegistry.get(${ID});
|
|
9673
9837
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${ID} };
|
|
@@ -9688,7 +9852,7 @@ ${resolveSource}
|
|
|
9688
9852
|
if (needsSibling) {
|
|
9689
9853
|
const siblingExpr = order === 'moveUp' ? 'entry.item.nextSibling' : 'entry.item.previousSibling';
|
|
9690
9854
|
return `
|
|
9691
|
-
// Reorder item: ${order}
|
|
9855
|
+
// Reorder item: ${oneLine(order)}
|
|
9692
9856
|
(function() {
|
|
9693
9857
|
const entry = app.itemRegistry.get(${ID});
|
|
9694
9858
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${ID} };
|
|
@@ -9698,7 +9862,7 @@ ${resolveSource}
|
|
|
9698
9862
|
})();`.trim();
|
|
9699
9863
|
}
|
|
9700
9864
|
return `
|
|
9701
|
-
// Reorder item: ${order}
|
|
9865
|
+
// Reorder item: ${oneLine(order)}
|
|
9702
9866
|
(function() {
|
|
9703
9867
|
const entry = app.itemRegistry.get(${ID});
|
|
9704
9868
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${ID} };
|
|
@@ -9788,7 +9952,7 @@ ${resolve}
|
|
|
9788
9952
|
// otherwise; a falsy answer is a refusal it did not raise.
|
|
9789
9953
|
if (r === false || r === null) {
|
|
9790
9954
|
return { success: false, action: 'apply', itemId: ${JSON.stringify(input.itemId)},
|
|
9791
|
-
error: 'the filter did not apply — ${input.filterName || ''} may not be available on this studio.' };
|
|
9955
|
+
error: 'the filter did not apply — ' + ${jsLiteral(input.filterName || '')} + ' may not be available on this studio.' };
|
|
9792
9956
|
}
|
|
9793
9957
|
return { success: true, action: 'apply', itemId: ${JSON.stringify(input.itemId)}, filter: ${JSON.stringify(input.filterName || '')} };
|
|
9794
9958
|
} catch (e) {
|
|
@@ -10143,7 +10307,7 @@ ${resolve}
|
|
|
10143
10307
|
// Get item details by ID
|
|
10144
10308
|
(function() {
|
|
10145
10309
|
const entry = app.itemRegistry.get(${JSON.stringify(input.itemId || '')});
|
|
10146
|
-
if (!entry || !entry.item) return { error: 'Item not found: ${input.itemId}
|
|
10310
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${jsLiteral(input.itemId)} };
|
|
10147
10311
|
const item = entry.item;
|
|
10148
10312
|
return {
|
|
10149
10313
|
success: true, action: 'get_by_id',
|
|
@@ -10443,8 +10607,8 @@ ${resolve}
|
|
|
10443
10607
|
${guard}
|
|
10444
10608
|
const entry = app.itemRegistry.get(${itemIdStr});
|
|
10445
10609
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
10446
|
-
app.deformPresets.apply(entry.item,
|
|
10447
|
-
return { success: true, action: 'apply', itemId: ${itemIdStr}, preset:
|
|
10610
|
+
app.deformPresets.apply(entry.item, ${jsLiteral(input.preset)}, ${JSON.stringify(params)});
|
|
10611
|
+
return { success: true, action: 'apply', itemId: ${itemIdStr}, preset: ${jsLiteral(input.preset)} };
|
|
10448
10612
|
})();`.trim();
|
|
10449
10613
|
}
|
|
10450
10614
|
case 'trigger': {
|
|
@@ -10464,8 +10628,8 @@ ${resolve}
|
|
|
10464
10628
|
${guard}
|
|
10465
10629
|
const entry = app.itemRegistry.get(${itemIdStr});
|
|
10466
10630
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
10467
|
-
app.deformPresets.trigger(entry.item,
|
|
10468
|
-
return { success: true, action: 'trigger', itemId: ${itemIdStr}, preset:
|
|
10631
|
+
app.deformPresets.trigger(entry.item, ${jsLiteral(input.preset)}, ${JSON.stringify(params)});
|
|
10632
|
+
return { success: true, action: 'trigger', itemId: ${itemIdStr}, preset: ${jsLiteral(input.preset)} };
|
|
10469
10633
|
})();`.trim();
|
|
10470
10634
|
}
|
|
10471
10635
|
case 'remove': {
|
|
@@ -10718,8 +10882,8 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
10718
10882
|
// Export sprite sheet
|
|
10719
10883
|
(async function() {
|
|
10720
10884
|
${guard}
|
|
10721
|
-
const result = await app.exportSpriteSheet(${JSON.stringify(input.spriteSheetId || '')}, { format:
|
|
10722
|
-
return { success: true, action: 'export', format:
|
|
10885
|
+
const result = await app.exportSpriteSheet(${JSON.stringify(input.spriteSheetId || '')}, { format: ${jsLiteral(input.format || 'png')}, download: true, includeMetadata: true });
|
|
10886
|
+
return { success: true, action: 'export', format: ${jsLiteral(input.format || 'png')} };
|
|
10723
10887
|
})();`.trim();
|
|
10724
10888
|
default:
|
|
10725
10889
|
return `(function() { return { error: 'Unknown sprite sheet action: ${input.action}' }; })();`;
|
|
@@ -10740,8 +10904,8 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
10740
10904
|
${guard}
|
|
10741
10905
|
const entry = app.itemRegistry.get(${itemIdStr});
|
|
10742
10906
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
10743
|
-
const behaviorId = app.interactionSystem.addContinuousBehavior(entry.item,
|
|
10744
|
-
return { success: true, action: 'add_behavior', itemId: ${itemIdStr}, behaviorType:
|
|
10907
|
+
const behaviorId = app.interactionSystem.addContinuousBehavior(entry.item, ${jsLiteral(input.behaviorType)}, ${params});
|
|
10908
|
+
return { success: true, action: 'add_behavior', itemId: ${itemIdStr}, behaviorType: ${jsLiteral(input.behaviorType)}, behaviorId };
|
|
10745
10909
|
})();`.trim();
|
|
10746
10910
|
}
|
|
10747
10911
|
case 'remove_behavior': {
|
|
@@ -10798,8 +10962,8 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
10798
10962
|
// Trigger interaction action
|
|
10799
10963
|
(function() {
|
|
10800
10964
|
${guard}
|
|
10801
|
-
app.interactionSystem.executeAction(
|
|
10802
|
-
return { success: true, action: 'trigger_action', actionType:
|
|
10965
|
+
app.interactionSystem.executeAction(${jsLiteral(input.actionType)}, ${params});
|
|
10966
|
+
return { success: true, action: 'trigger_action', actionType: ${jsLiteral(input.actionType)} };
|
|
10803
10967
|
})();`.trim();
|
|
10804
10968
|
}
|
|
10805
10969
|
case 'get_state':
|
|
@@ -11226,14 +11390,14 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11226
11390
|
}[input.action];
|
|
11227
11391
|
const id = JSON.stringify(input.itemId);
|
|
11228
11392
|
return `
|
|
11229
|
-
// Arrange ${input.itemId} → ${input.action}
|
|
11393
|
+
// Arrange ${oneLine(input.itemId)} → ${oneLine(input.action)}
|
|
11230
11394
|
(function() {
|
|
11231
11395
|
const item = app.getItemById(${id});
|
|
11232
11396
|
if (!item) return { success: false, error: 'Item not found: ' + ${id} };
|
|
11233
11397
|
if (typeof app.${method} !== 'function') return { success: false, error: 'app.${method} unavailable — update FxTool' };
|
|
11234
11398
|
app.select(${id});
|
|
11235
11399
|
app.${method}();
|
|
11236
|
-
return { success: true, itemId: ${id}, action:
|
|
11400
|
+
return { success: true, itemId: ${id}, action: ${jsLiteral(input.action)} };
|
|
11237
11401
|
})();`.trim();
|
|
11238
11402
|
}
|
|
11239
11403
|
/**
|
|
@@ -11266,7 +11430,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11266
11430
|
...(input.queries !== undefined ? { queries: input.queries } : {}),
|
|
11267
11431
|
});
|
|
11268
11432
|
return `
|
|
11269
|
-
// Detect objects${input.queries !== undefined ? ' (open-vocabulary / OWL-ViT)' : ''}${input.asNodes ? ' (asNodes — relational compositing)' : ''}
|
|
11433
|
+
// Detect objects${oneLine(input.queries !== undefined ? ' (open-vocabulary / OWL-ViT)' : '')}${oneLine(input.asNodes ? ' (asNodes — relational compositing)' : '')}
|
|
11270
11434
|
(async function() {
|
|
11271
11435
|
if (typeof app.detectObjects !== 'function') {
|
|
11272
11436
|
return { success: false, error: 'app.detectObjects unavailable — update FxTool to a build that exposes on-device object detection on app' };
|
|
@@ -11296,7 +11460,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11296
11460
|
...(input.id !== undefined ? { id: input.id } : {}),
|
|
11297
11461
|
};
|
|
11298
11462
|
return `
|
|
11299
|
-
// Styled scene${input.style ? ` (${input.style.replace(/[\r\n]/g, ' ')})` : ''}
|
|
11463
|
+
// Styled scene${input.style ? ` (${oneLine(input.style.replace(/[\r\n]/g, ' '))})` : ''}
|
|
11300
11464
|
(async function() {
|
|
11301
11465
|
if (typeof app.styledScene !== 'function') { return { success: false, error: 'app.styledScene unavailable — update FxTool' }; }
|
|
11302
11466
|
const r = await app.styledScene(${JSON.stringify(input.spec ?? {})}, ${JSON.stringify(opts)});
|
|
@@ -11316,7 +11480,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11316
11480
|
...(input.threshold !== undefined ? { threshold: input.threshold } : {}),
|
|
11317
11481
|
});
|
|
11318
11482
|
return `
|
|
11319
|
-
// Extract object${input.label ? `
|
|
11483
|
+
// Extract object${input.label ? ` ${jsLiteral(input.label)}` : ''} from the image
|
|
11320
11484
|
(async function() {
|
|
11321
11485
|
if (typeof app.extractObject !== 'function') {
|
|
11322
11486
|
return { success: false, error: 'app.extractObject unavailable — update FxTool to a build that exposes on-device object detection on app' };
|
|
@@ -11406,7 +11570,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11406
11570
|
if (input.action === 'break_apart') {
|
|
11407
11571
|
const idExpr = JSON.stringify(input.itemId || '');
|
|
11408
11572
|
return `
|
|
11409
|
-
// Break apart ${input.itemId || ''} into movable parts
|
|
11573
|
+
// Break apart ${oneLine(input.itemId || '')} into movable parts
|
|
11410
11574
|
(function() {
|
|
11411
11575
|
if (typeof app.breakApart !== 'function') return { success: false, error: 'app.breakApart unavailable — update FxTool to a build with SVG/group segmentation' };
|
|
11412
11576
|
const target = app.getItemById(${idExpr});
|
|
@@ -11421,7 +11585,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11421
11585
|
if (input.action === 'ungroup') {
|
|
11422
11586
|
const idExpr = JSON.stringify(input.groupId || '');
|
|
11423
11587
|
return `
|
|
11424
|
-
// Ungroup ${input.groupId || ''}
|
|
11588
|
+
// Ungroup ${oneLine(input.groupId || '')}
|
|
11425
11589
|
(function() {
|
|
11426
11590
|
if (!app.groupManager) return { success: false, error: 'GroupManager not available — update FxTool' };
|
|
11427
11591
|
const gid = ${idExpr};
|
|
@@ -11435,7 +11599,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11435
11599
|
const idsArrayExpr = JSON.stringify(input.itemIds ?? []);
|
|
11436
11600
|
const nameExpr = input.groupName !== undefined ? JSON.stringify(input.groupName) : 'undefined';
|
|
11437
11601
|
return `
|
|
11438
|
-
// Group ${(input.itemIds || []).length} items${input.groupName ? ` as
|
|
11602
|
+
// Group ${oneLine((input.itemIds || []).length)} items${input.groupName ? ` as ${jsLiteral(input.groupName)}` : ''}
|
|
11439
11603
|
(function() {${this.groupCodeBody(idsArrayExpr, nameExpr)}
|
|
11440
11604
|
})();`.trim();
|
|
11441
11605
|
}
|
|
@@ -11485,7 +11649,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11485
11649
|
const duration = input.duration ?? 5;
|
|
11486
11650
|
const n = Math.min(1200, Math.max(3, Math.round(duration * fps)));
|
|
11487
11651
|
return `
|
|
11488
|
-
// Accessibility measurements (${checks.join(', ')})
|
|
11652
|
+
// Accessibility measurements (${oneLine(checks.join(', '))})
|
|
11489
11653
|
(async function() {
|
|
11490
11654
|
const out = { success: true, fps: ${fps}, duration: ${duration} };
|
|
11491
11655
|
const rgba = function(c) {
|
|
@@ -11642,7 +11806,7 @@ ${checks.includes('contrast') ? ` // SAMPLED OVER TIME (round 12, 12.9): text i
|
|
|
11642
11806
|
const mime = sheet.format === 'png' ? 'image/png' : 'image/jpeg';
|
|
11643
11807
|
const S = (v) => JSON.stringify(v);
|
|
11644
11808
|
return `
|
|
11645
|
-
// Contact sheet of ${n} frame(s)
|
|
11809
|
+
// Contact sheet of ${oneLine(n)} frame(s)
|
|
11646
11810
|
(async function() {
|
|
11647
11811
|
if (typeof app.captureFramesAt !== 'function') {
|
|
11648
11812
|
return { success: false, error: 'app.captureFramesAt unavailable — update FxTool to a build with the deterministic capture entrypoint' };
|
|
@@ -11718,7 +11882,7 @@ ${checks.includes('contrast') ? ` // SAMPLED OVER TIME (round 12, 12.9): text i
|
|
|
11718
11882
|
// tool call that screenshots next wants that to have happened.
|
|
11719
11883
|
const engineOptsJson = JSON.stringify({ canvas: input.canvas, defaultGeometry: input.defaultGeometry, awaitImages: true });
|
|
11720
11884
|
return `
|
|
11721
|
-
// Instantiate ontology → scene (${ops.filter((o) => o.op === 'create').length} items, ${ops.filter((o) => o.op === 'addRelation').length} relations)
|
|
11885
|
+
// Instantiate ontology → scene (${oneLine(ops.filter((o) => o.op === 'create').length)} items, ${oneLine(ops.filter((o) => o.op === 'addRelation').length)} relations)
|
|
11722
11886
|
(async function() {
|
|
11723
11887
|
// Engine door first: the studio's own OntologyCompiler reads every node facet
|
|
11724
11888
|
// (geometry, paint, text, the keyframe track, masks) and builds through the
|
|
@@ -11803,10 +11967,27 @@ ${checks.includes('contrast') ? ` // SAMPLED OVER TIME (round 12, 12.9): text i
|
|
|
11803
11967
|
return { success: false, error: 'app.lintScene unavailable — update FxTool to a build with the relational linter (S12-E2)' };
|
|
11804
11968
|
}
|
|
11805
11969
|
const result = app.lintScene(${optsJson});
|
|
11970
|
+
// THE RELATIONS AS BOUND, so a later agent can find the one to edit without exporting the scene to read it:
|
|
11971
|
+
// from, to, type and params, capped. And the custom relation definitions registered on this page, as given.
|
|
11972
|
+
const __rels = [];
|
|
11973
|
+
let __relCount = 0;
|
|
11974
|
+
try {
|
|
11975
|
+
const __rr = app.relationRegistry;
|
|
11976
|
+
const __active = __rr && typeof __rr.queryActive === 'function' ? __rr.queryActive() : [];
|
|
11977
|
+
__active.forEach((e) => (e.associations || []).forEach((a) => {
|
|
11978
|
+
__relCount++;
|
|
11979
|
+
if (__rels.length >= ${RELATIONS_READBACK_CAP}) return;
|
|
11980
|
+
const __name = e.item && e.item.data && e.item.data.name;
|
|
11981
|
+
__rels.push({ from: e.itemId, ...(__name ? { fromName: __name } : {}), to: a.target === undefined ? null : a.target, type: a.relation, params: a.params || {} });
|
|
11982
|
+
}));
|
|
11983
|
+
} catch (e) { /* the relations are an addition; the lint stands without them */ }
|
|
11984
|
+
const __custom = (typeof window !== 'undefined' && window.__ppCustomRelations) ? Object.values(window.__ppCustomRelations) : [];
|
|
11806
11985
|
// warnings: what the engine's own rules found (TEXT_CLIPS_FRAME: a centre-anchored text running past the
|
|
11807
11986
|
// frame edge). Passed through when the studio reports them; an older studio has none to pass.
|
|
11808
11987
|
return { success: true, density: result.density, suggestions: result.suggestions,
|
|
11809
|
-
...(Array.isArray(result.warnings) && result.warnings.length ? { warnings: result.warnings } : {})
|
|
11988
|
+
...(Array.isArray(result.warnings) && result.warnings.length ? { warnings: result.warnings } : {}),
|
|
11989
|
+
relations: __rels, ...(__relCount > __rels.length ? { relationsShown: __rels.length, relationCount: __relCount } : {}),
|
|
11990
|
+
...(__custom.length ? { customRelations: __custom } : {}) };
|
|
11810
11991
|
})();`.trim();
|
|
11811
11992
|
}
|
|
11812
11993
|
/**
|
|
@@ -11837,11 +12018,11 @@ ${checks.includes('contrast') ? ` // SAMPLED OVER TIME (round 12, 12.9): text i
|
|
|
11837
12018
|
if (u.indexOf('data:') !== 0) return u;
|
|
11838
12019
|
const comma = u.indexOf(',');
|
|
11839
12020
|
const meta = u.slice(5, comma);
|
|
11840
|
-
const mime = meta.split(';')[0] ||
|
|
12021
|
+
const mime = meta.split(';')[0] || ${jsLiteral(kind === 'audio' ? 'audio/mpeg' : 'video/mp4')};
|
|
11841
12022
|
const body = u.slice(comma + 1);
|
|
11842
12023
|
const bin = /;base64/i.test(meta) ? atob(body) : decodeURIComponent(body);
|
|
11843
12024
|
const bytes = Uint8Array.from(bin, function(c) { return c.charCodeAt(0); });
|
|
11844
|
-
const ext = (mime.split('/')[1] ||
|
|
12025
|
+
const ext = (mime.split('/')[1] || ${jsLiteral(kind === 'audio' ? 'mp3' : 'mp4')}).replace(/[^a-z0-9]/gi, '');
|
|
11845
12026
|
return new File([bytes], '${kind}.' + ext, { type: mime });
|
|
11846
12027
|
})(${JSON.stringify(url ?? '')})`;
|
|
11847
12028
|
// TWO ID SPACES, AND THE CALLER HOLDS THE WRONG ONE.
|
|
@@ -11950,7 +12131,7 @@ ${input.timeOffset !== undefined ? ` // Moving the clip is the 4th argument, wh
|
|
|
11950
12131
|
// refusals come back as {ok:false, reason} and are errors here.
|
|
11951
12132
|
case 'split':
|
|
11952
12133
|
return `
|
|
11953
|
-
// Split a media clip at canvas time ${input.at}
|
|
12134
|
+
// Split a media clip at canvas time ${oneLine(input.at)}
|
|
11954
12135
|
(async function() {
|
|
11955
12136
|
${guard}
|
|
11956
12137
|
if (typeof A.splitMedia !== 'function') { return { success: false, action: 'split', error: 'this studio is too old to split a clip (no splitMedia) — nothing was changed.' }; }
|
|
@@ -11960,7 +12141,7 @@ ${guard}
|
|
|
11960
12141
|
})();`.trim();
|
|
11961
12142
|
case 'add_transition':
|
|
11962
12143
|
return `
|
|
11963
|
-
// ${input.transition?.type ?? 'crossfade'} between ${input.fromItemId} and ${input.toItemId}
|
|
12144
|
+
// ${oneLine(input.transition?.type ?? 'crossfade')} between ${oneLine(input.fromItemId)} and ${oneLine(input.toItemId)}
|
|
11964
12145
|
(function() {
|
|
11965
12146
|
${guard}
|
|
11966
12147
|
if (typeof A.addTransition !== 'function') { return { success: false, action: 'add_transition', error: 'this studio is too old for clip transitions (no addTransition) — nothing was changed.' }; }
|
|
@@ -12082,7 +12263,7 @@ ${guard}
|
|
|
12082
12263
|
...(input.live !== undefined ? { live: input.live } : {}),
|
|
12083
12264
|
});
|
|
12084
12265
|
return `
|
|
12085
|
-
// Track matte: the matte item's ${input.channel || 'luma'} drives the target's alpha${input.live ? ' (LIVE — re-cuts as the matte animates)' : ''}
|
|
12266
|
+
// Track matte: the matte item's ${oneLine(input.channel || 'luma')} drives the target's alpha${oneLine(input.live ? ' (LIVE — re-cuts as the matte animates)' : '')}
|
|
12086
12267
|
(async function() {
|
|
12087
12268
|
// A media id (vraster_ / araster_, what upload returns) is resolved to the
|
|
12088
12269
|
// registry id these app facades take (round 10, 10.12).
|
|
@@ -12167,7 +12348,7 @@ ${guard}
|
|
|
12167
12348
|
...(input.fontSize !== undefined ? { fontSize: input.fontSize } : {}),
|
|
12168
12349
|
});
|
|
12169
12350
|
return `
|
|
12170
|
-
// Display text style: ${input.styleKey} — stacked-layer title, id adopted from the text
|
|
12351
|
+
// Display text style: ${oneLine(input.styleKey)} — stacked-layer title, id adopted from the text
|
|
12171
12352
|
(async function() {
|
|
12172
12353
|
if (typeof app.applyTextStyle !== 'function') { return { success: false, error: 'app.applyTextStyle unavailable — update FxTool to a text-styles build' }; }
|
|
12173
12354
|
// LOAD THE FACES FIRST. The decorative stylesheet is fetched on pointerenter
|
|
@@ -12318,7 +12499,7 @@ ${guard}
|
|
|
12318
12499
|
const S = (v) => JSON.stringify(v);
|
|
12319
12500
|
const id = S(input.itemId);
|
|
12320
12501
|
return `
|
|
12321
|
-
// Choreograph ${input.beats.length} beat(s) for ${input.itemId}
|
|
12502
|
+
// Choreograph ${oneLine(input.beats.length)} beat(s) for ${oneLine(input.itemId)}
|
|
12322
12503
|
(function() {
|
|
12323
12504
|
const __it = (app.getItemById && app.getItemById(${id}))
|
|
12324
12505
|
|| (app.itemRegistry && app.itemRegistry.get && (app.itemRegistry.get(${id}) || {}).item);
|
|
@@ -12426,7 +12607,7 @@ ${input.sound ? ` // SOUND ON THE ACTION (D68): one placed sound per moment the
|
|
|
12426
12607
|
const cam = input.camera === true ? { fill: 0.45 } : (input.camera || null);
|
|
12427
12608
|
const sound = input.sound === true ? 'whoosh' : (input.sound || null);
|
|
12428
12609
|
return `
|
|
12429
|
-
// Morph sequence: ${states.length} state(s)
|
|
12610
|
+
// Morph sequence: ${oneLine(states.length)} state(s)
|
|
12430
12611
|
(function() {
|
|
12431
12612
|
const src = app.getItemById && app.getItemById(${S(input.itemId)});
|
|
12432
12613
|
if (!src) { return { success: false, error: ${S(input.itemId)} + ' is not on the canvas.' }; }
|
|
@@ -12903,7 +13084,7 @@ ${cam ? ` // The camera rides the SAME timing: framing each state as its morph
|
|
|
12903
13084
|
}
|
|
12904
13085
|
})();
|
|
12905
13086
|
return `
|
|
12906
|
-
// Shader graph: ${input.action}
|
|
13087
|
+
// Shader graph: ${oneLine(input.action)}
|
|
12907
13088
|
(async function() {
|
|
12908
13089
|
${head}
|
|
12909
13090
|
${body}
|
|
@@ -12924,7 +13105,7 @@ ${body}
|
|
|
12924
13105
|
// or brush-only medium with the reason. The Paper item it returns is not
|
|
12925
13106
|
// passed back (it cannot cross the bridge); its id is.
|
|
12926
13107
|
return `
|
|
12927
|
-
// Medium: apply ${input.medium}
|
|
13108
|
+
// Medium: apply ${oneLine(input.medium)}
|
|
12928
13109
|
(async function() {
|
|
12929
13110
|
if (typeof app.applyMedium !== 'function') { return { success: false, error: 'app.applyMedium unavailable — update FxTool' }; }
|
|
12930
13111
|
const r = await app.applyMedium(${S(input.itemId)}, ${S(input.medium)}, ${S(input.options ?? {})});
|
|
@@ -13118,7 +13299,7 @@ ${body}
|
|
|
13118
13299
|
...(input.keepSource !== undefined ? { keepSource: input.keepSource } : {}),
|
|
13119
13300
|
});
|
|
13120
13301
|
return `
|
|
13121
|
-
// Text effect: ${input.effect} — the text becomes one animated item PER CHARACTER
|
|
13302
|
+
// Text effect: ${oneLine(input.effect)} — the text becomes one animated item PER CHARACTER
|
|
13122
13303
|
(async function() {
|
|
13123
13304
|
if (typeof app.applyTextEffect !== 'function') { return { success: false, error: 'app.applyTextEffect unavailable — update FxTool to a text-effects build' }; }
|
|
13124
13305
|
const r = await app.applyTextEffect(${S(input.itemId)}, ${S(input.effect)}, ${opts});
|
|
@@ -13190,7 +13371,7 @@ ${body}
|
|
|
13190
13371
|
...(input.keepSource !== undefined ? { keepSource: input.keepSource } : {}),
|
|
13191
13372
|
});
|
|
13192
13373
|
return `
|
|
13193
|
-
// Shatter ${input.itemId} into tiles (inert until animated — that is the point)
|
|
13374
|
+
// Shatter ${oneLine(input.itemId)} into tiles (inert until animated — that is the point)
|
|
13194
13375
|
(function() {
|
|
13195
13376
|
if (typeof app.shatterImage !== 'function') { return { success: false, error: 'app.shatterImage unavailable — update FxTool to a shatter-capable build' }; }
|
|
13196
13377
|
const r = app.shatterImage(${S(input.itemId)}, ${opts});
|
|
@@ -13325,7 +13506,10 @@ ${body}
|
|
|
13325
13506
|
const needWorld = ` if (!app._world3d) { return { success: false, error: "no 3D world — call pinepaper_world3d create first" }; }`;
|
|
13326
13507
|
switch (input.action) {
|
|
13327
13508
|
case 'create': {
|
|
13328
|
-
|
|
13509
|
+
if (input.control === true && input.character === false) {
|
|
13510
|
+
return `({ success: false, error: ${jsLiteral('control: true walks the character, and character: false leaves it out. Nothing was made. Drop one of the two.')} });`;
|
|
13511
|
+
}
|
|
13512
|
+
const opts = S({ ...(input.character !== undefined ? { character: input.character } : {}), ...(input.control === true ? { control: true } : {}) });
|
|
13329
13513
|
return `
|
|
13330
13514
|
// World3D: create — terrain, sky, shadows, walkable character, under the Paper canvas
|
|
13331
13515
|
(async function() {
|
|
@@ -13338,7 +13522,11 @@ ${body}
|
|
|
13338
13522
|
// params is WORLD_SCHEMA: an ARRAY of {path, kind, doc}. Object.keys on it
|
|
13339
13523
|
// returned "0", "1", "2"… — the indexes, not a single usable key.
|
|
13340
13524
|
const keys = !d ? [] : Array.isArray(d.params) ? d.params.map(function (e) { return e && e.path; }).filter(Boolean) : Object.keys(d.params || d);
|
|
13341
|
-
|
|
13525
|
+
// What the world reports, not what was asked: an engine without the option takes the world and ignores control.
|
|
13526
|
+
const result = { success: true, action: 'create', spec: ${S(input.spec ?? 'forest')}, configurableKeys: keys };${input.control === true ? `
|
|
13527
|
+
result.control = !!(app._world3d && app._world3d.controlEnabled);
|
|
13528
|
+
if (!result.control) result.warning = 'control was asked for and this studio did not turn it on: the character cannot be walked with the keyboard. Update PinePaper Studio.';` : ''}
|
|
13529
|
+
return result;
|
|
13342
13530
|
})();`.trim();
|
|
13343
13531
|
}
|
|
13344
13532
|
case 'describe':
|
|
@@ -13368,7 +13556,7 @@ ${needWorld}
|
|
|
13368
13556
|
...(input.live !== undefined ? { live: input.live } : {}),
|
|
13369
13557
|
});
|
|
13370
13558
|
return `
|
|
13371
|
-
// World3D: put an actor on the stage${input.live ? ' (live sprite — a rigged character PERFORMS, not a photograph of itself)' : ''}
|
|
13559
|
+
// World3D: put an actor on the stage${oneLine(input.live ? ' (live sprite — a rigged character PERFORMS, not a photograph of itself)' : '')}
|
|
13372
13560
|
(function() {
|
|
13373
13561
|
if (typeof app.addWorldActor !== 'function') { return { success: false, error: 'app.addWorldActor unavailable — update FxTool' }; }
|
|
13374
13562
|
${needWorld}
|
|
@@ -13471,7 +13659,7 @@ ${needWorld}
|
|
|
13471
13659
|
mesh.uniforms = uniforms;
|
|
13472
13660
|
}
|
|
13473
13661
|
return `
|
|
13474
|
-
// World3D: ${input.action} — a canvas path becomes real geometry
|
|
13662
|
+
// World3D: ${oneLine(input.action)} — a canvas path becomes real geometry
|
|
13475
13663
|
(async function() {
|
|
13476
13664
|
// @engine-methods extrudeToMesh latheToMesh importOBJToWorld importGLTFToWorld
|
|
13477
13665
|
if (typeof app.${fn} !== 'function') { return { success: false, error: 'app.${fn} unavailable — update FxTool to a world3d-capable build' }; }
|
|
@@ -13578,7 +13766,7 @@ ${needWorld}
|
|
|
13578
13766
|
case 'import_gltf': {
|
|
13579
13767
|
const fn = input.action === 'import_obj' ? 'importOBJToWorld' : 'importGLTFToWorld';
|
|
13580
13768
|
return `
|
|
13581
|
-
// World3D: ${input.action}
|
|
13769
|
+
// World3D: ${oneLine(input.action)}
|
|
13582
13770
|
(async function() {
|
|
13583
13771
|
// @engine-methods extrudeToMesh latheToMesh importOBJToWorld importGLTFToWorld
|
|
13584
13772
|
if (typeof app.${fn} !== 'function') { return { success: false, error: 'app.${fn} unavailable — update FxTool to a world3d-capable build' }; }
|
|
@@ -13780,7 +13968,7 @@ ${needWorld}
|
|
|
13780
13968
|
generateDesignCompose(scene, ops, style, sizeWarning, report = {}) {
|
|
13781
13969
|
const S = (v) => JSON.stringify(v);
|
|
13782
13970
|
return `
|
|
13783
|
-
// Design: compose a ${style} scene — ${ops.length} items, laid out server-side
|
|
13971
|
+
// Design: compose a ${oneLine(style)} scene — ${oneLine(ops.length)} items, laid out server-side
|
|
13784
13972
|
(function() {
|
|
13785
13973
|
if (typeof app.create !== 'function') { return { success: false, error: 'app.create unavailable — update FxTool' }; }
|
|
13786
13974
|
const size = ${S({ width: scene.width, height: scene.height })};
|
|
@@ -13860,7 +14048,7 @@ ${needWorld}
|
|
|
13860
14048
|
}
|
|
13861
14049
|
const fn = action === 'figure' ? 'stickFigure' : 'stickSet';
|
|
13862
14050
|
return `
|
|
13863
|
-
// Stick: ${action}
|
|
14051
|
+
// Stick: ${oneLine(action)}
|
|
13864
14052
|
(async function() {
|
|
13865
14053
|
// @engine-methods stickFigure stickSet
|
|
13866
14054
|
if (typeof app.${fn} !== 'function') { return { success: false, error: 'app.${fn} unavailable — update FxTool (the stick kit is vendored from mcp-cloud)' }; }
|
|
@@ -13954,7 +14142,7 @@ ${needWorld}
|
|
|
13954
14142
|
case 'export_dotlottie': {
|
|
13955
14143
|
const fn = input.action === 'export_lottie' ? 'exportLottie' : 'exportDotLottie';
|
|
13956
14144
|
return `
|
|
13957
|
-
// Interchange: ${fn}
|
|
14145
|
+
// Interchange: ${oneLine(fn)}
|
|
13958
14146
|
(async function() {
|
|
13959
14147
|
${guard(fn)}
|
|
13960
14148
|
${TO_DATA_URL_JS}
|
|
@@ -14071,7 +14259,7 @@ ${guard('exportPNGSequence')}
|
|
|
14071
14259
|
const S = (v) => JSON.stringify(v);
|
|
14072
14260
|
const guard = (fn) => ` if (typeof app.${fn} !== 'function') { return { success: false, error: 'app.${fn} unavailable — update FxTool' }; }`;
|
|
14073
14261
|
const simple = (fn, args, key, label) => key === 'played' ? `
|
|
14074
|
-
// Sound: ${label}
|
|
14262
|
+
// Sound: ${oneLine(label)}
|
|
14075
14263
|
(function() {
|
|
14076
14264
|
${guard(fn)}
|
|
14077
14265
|
// A play that returns no voice played nothing, and reported success (gate
|
|
@@ -14080,7 +14268,7 @@ ${guard(fn)}
|
|
|
14080
14268
|
if (__v == null) { return { success: false, error: 'nothing played: the studio returned no voice (it may have no audio output). Placed sounds (create, sequence) and render_soundtrack work without one.' }; }
|
|
14081
14269
|
return { success: true, played: __v };
|
|
14082
14270
|
})();`.trim() : `
|
|
14083
|
-
// Sound: ${label}
|
|
14271
|
+
// Sound: ${oneLine(label)}
|
|
14084
14272
|
(function() {
|
|
14085
14273
|
${guard(fn)}
|
|
14086
14274
|
return { success: true, ${key}: app.${fn}(${args}) };
|
|
@@ -14102,7 +14290,7 @@ ${guard(fn)}
|
|
|
14102
14290
|
: simple('playSoundFromText', S(input.text), 'played', 'a described sound');
|
|
14103
14291
|
case 'from_text':
|
|
14104
14292
|
return `
|
|
14105
|
-
// Sound: resolve
|
|
14293
|
+
// Sound: resolve ${jsLiteral(input.text)} to a spec WITHOUT playing it
|
|
14106
14294
|
(function() {
|
|
14107
14295
|
${guard('buildSoundFromText')}
|
|
14108
14296
|
const spec = app.buildSoundFromText(${S(input.text)});
|
|
@@ -14145,7 +14333,7 @@ ${guard('buildSoundFromText')}
|
|
|
14145
14333
|
// checking the method a caller needs. It extracted only
|
|
14146
14334
|
// renderSoundtrackWav until this line existed.
|
|
14147
14335
|
return `
|
|
14148
|
-
// Sound: define ${input.name} at runtime
|
|
14336
|
+
// Sound: define ${oneLine(input.name)} at runtime
|
|
14149
14337
|
(function() {
|
|
14150
14338
|
${guard(fn)}
|
|
14151
14339
|
var name;
|
|
@@ -14329,7 +14517,7 @@ ${guard('timbreFromPath')}
|
|
|
14329
14517
|
opts[k] = input[k];
|
|
14330
14518
|
}
|
|
14331
14519
|
return `
|
|
14332
|
-
// Motion: ${input.motion} on ${input.itemIds?.length ? `${input.itemIds.length} items` : input.itemId}
|
|
14520
|
+
// Motion: ${oneLine(input.motion)} on ${input.itemIds?.length ? `${oneLine(input.itemIds.length)} items` : input.itemId}
|
|
14333
14521
|
(async function() {
|
|
14334
14522
|
if (typeof app.animateItems !== 'function') { return { success: false, error: 'app.animateItems unavailable — update FxTool' }; }
|
|
14335
14523
|
const r = await app.animateItems(${target}, ${S(opts)});
|
|
@@ -14367,7 +14555,7 @@ ${guard('timbreFromPath')}
|
|
|
14367
14555
|
switch (input.action) {
|
|
14368
14556
|
case 'boolean':
|
|
14369
14557
|
return `
|
|
14370
|
-
// Path boolean: ${input.op} — CONSUMES its operands and leaves one result
|
|
14558
|
+
// Path boolean: ${oneLine(input.op)} — CONSUMES its operands and leaves one result
|
|
14371
14559
|
(function() {
|
|
14372
14560
|
${guard('booleanOp')}${pass(`app.booleanOp(${S(input.itemIds)}, ${S(input.op)})`)}
|
|
14373
14561
|
})();`.trim();
|
|
@@ -14391,7 +14579,7 @@ ${guard('togglePathClosed')}${pass(`app.togglePathClosed(${S(input.itemId)})`)}
|
|
|
14391
14579
|
})();`.trim();
|
|
14392
14580
|
case 'pattern':
|
|
14393
14581
|
return `
|
|
14394
|
-
// Pattern: repeat the item — ${input.kind}
|
|
14582
|
+
// Pattern: repeat the item — ${oneLine(input.kind)}
|
|
14395
14583
|
(function() {
|
|
14396
14584
|
${guard('patternFrom')}${pass(`app.patternFrom(${S(input.itemId)}, ${S(input.kind)}, ${S(input.pattern ?? {})})`)}
|
|
14397
14585
|
})();`.trim();
|
|
@@ -14427,7 +14615,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14427
14615
|
generateCropImage(input) {
|
|
14428
14616
|
const opts = input.aspectRatio ? JSON.stringify({ aspectRatio: input.aspectRatio }) : '{}';
|
|
14429
14617
|
return `
|
|
14430
|
-
// Crop image ${input.itemId}
|
|
14618
|
+
// Crop image ${oneLine(input.itemId)}
|
|
14431
14619
|
(async function() {
|
|
14432
14620
|
if (typeof app.cropImage !== 'function') { return { success: false, error: 'app.cropImage unavailable — update FxTool to a build with one-shot image ops' }; }
|
|
14433
14621
|
const result = await app.cropImage(${JSON.stringify(input.itemId)}, ${JSON.stringify(input.rect)}, ${opts});
|
|
@@ -14446,7 +14634,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14446
14634
|
...(input.smoothing !== undefined ? { smoothing: input.smoothing } : {}),
|
|
14447
14635
|
});
|
|
14448
14636
|
return `
|
|
14449
|
-
// Chroma-key image ${input.itemId}
|
|
14637
|
+
// Chroma-key image ${oneLine(input.itemId)}
|
|
14450
14638
|
(async function() {
|
|
14451
14639
|
if (typeof app.applyChromaKey !== 'function') { return { success: false, error: 'app.applyChromaKey unavailable — update FxTool to a build with one-shot image ops' }; }
|
|
14452
14640
|
const result = await app.applyChromaKey(${JSON.stringify(input.itemId)}, ${opts});
|
|
@@ -14862,7 +15050,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14862
15050
|
const argsJson = JSON.stringify(input.args ?? []);
|
|
14863
15051
|
const createAsJson = input.createAs ? JSON.stringify(input.createAs) : 'null';
|
|
14864
15052
|
return `
|
|
14865
|
-
// Geometry construction: ${input.operation}
|
|
15053
|
+
// Geometry construction: ${oneLine(input.operation)}
|
|
14866
15054
|
(function() {
|
|
14867
15055
|
const g = app.geometry;
|
|
14868
15056
|
if (!g || typeof g[${op}] !== 'function') {
|
|
@@ -14924,7 +15112,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14924
15112
|
const optsJson = JSON.stringify(input);
|
|
14925
15113
|
const kindJson = JSON.stringify(input.kind);
|
|
14926
15114
|
return `
|
|
14927
|
-
// Equation-driven path: ${input.kind}${input.preset ? ' ' + input.preset : ''}
|
|
15115
|
+
// Equation-driven path: ${oneLine(input.kind)}${oneLine(input.preset ? ' ' + input.preset : '')}
|
|
14928
15116
|
(function() {
|
|
14929
15117
|
if (typeof app.createEquationPath !== 'function') {
|
|
14930
15118
|
return { success: false, error: 'app.createEquationPath unavailable — update FxTool to a build with the Expression IR equation-path API (S10 B5)' };
|
|
@@ -14948,7 +15136,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14948
15136
|
const nameJson = JSON.stringify(input.name);
|
|
14949
15137
|
const optsJson = JSON.stringify({ payloadType: input.payloadType, x: input.x, y: input.y, ...(input.at !== undefined ? { at: input.at } : {}) });
|
|
14950
15138
|
return `
|
|
14951
|
-
// Create event: ${input.name}
|
|
15139
|
+
// Create event: ${oneLine(input.name)}
|
|
14952
15140
|
(function() {
|
|
14953
15141
|
if (typeof app.createEvent !== 'function') {
|
|
14954
15142
|
return { success: false, error: 'app.createEvent unavailable — update FxTool to a build with the event system (S11)' };
|
|
@@ -14963,7 +15151,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14963
15151
|
const eventIdJson = JSON.stringify(input.eventId);
|
|
14964
15152
|
const payloadJson = JSON.stringify(input.payload ?? null);
|
|
14965
15153
|
return `
|
|
14966
|
-
// Pulse event: ${input.eventId}
|
|
15154
|
+
// Pulse event: ${oneLine(input.eventId)}
|
|
14967
15155
|
(function() {
|
|
14968
15156
|
if (typeof app.pulseEvent !== 'function') {
|
|
14969
15157
|
return { success: false, error: 'app.pulseEvent unavailable — update FxTool to a build with the event system (S11)' };
|
|
@@ -15108,7 +15296,7 @@ return {
|
|
|
15108
15296
|
*/
|
|
15109
15297
|
export function generateAutoKeyCode(itemId, props, atTime, easing) {
|
|
15110
15298
|
return `
|
|
15111
|
-
// Auto-key ${itemId} at ${atTime}s
|
|
15299
|
+
// Auto-key ${oneLine(itemId)} at ${oneLine(atTime)}s
|
|
15112
15300
|
(function() {
|
|
15113
15301
|
const item = app.getItemById(${JSON.stringify(itemId)});
|
|
15114
15302
|
if (!item) return { success: false, error: ${JSON.stringify(`no item "${itemId}" on the canvas — check the id with pinepaper_get_items.`)} };
|
|
@@ -15166,7 +15354,7 @@ export function generateAutoKeyCode(itemId, props, atTime, easing) {
|
|
|
15166
15354
|
*/
|
|
15167
15355
|
export function generateQueryMutationsCode(itemId, property, range, fps = 30) {
|
|
15168
15356
|
return `
|
|
15169
|
-
// Keyframe series for ${itemId}${property ? '.' + property : ''}
|
|
15357
|
+
// Keyframe series for ${oneLine(itemId)}${oneLine(property ? '.' + property : '')}
|
|
15170
15358
|
(function() {
|
|
15171
15359
|
const item = app.getItemById(${JSON.stringify(itemId)});
|
|
15172
15360
|
if (!item) return { success: false, error: ${JSON.stringify(`no item "${itemId}" on the canvas.`)} };
|