@pinepaper.studio/mcp-server 1.6.25 → 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 +108 -7
- package/SECURITY.md +15 -1
- package/dist/agent/types.d.ts +3 -3
- package/dist/browser/puppeteer-controller.d.ts +12 -1
- package/dist/browser/puppeteer-controller.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.js +105 -15
- 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 +65 -3
- 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 +459 -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 -9
|
@@ -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
|
|
@@ -6127,6 +6287,28 @@ ${stillTime === undefined ? ` if (['png', 'jpg', 'webp', 'svg', 'pdf'].indexOf(
|
|
|
6127
6287
|
}
|
|
6128
6288
|
|
|
6129
6289
|
if (result && result.success) Object.assign(result, fidelity(format));
|
|
6290
|
+
// MOTION FROM RELATIONS IS MOTION. The studio's check counts keyframe tracks
|
|
6291
|
+
// and simple animations only, so an orbit (or attached_to a moving item, a
|
|
6292
|
+
// generator, the camera) exported with animated:false while the frames moved,
|
|
6293
|
+
// and callers rebuilt the scene with keyframes. When it says false, the
|
|
6294
|
+
// frames decide: four captures across the export. The times are uneven so a
|
|
6295
|
+
// loop cannot land on whole periods and look still. After the export, so it
|
|
6296
|
+
// cannot touch the file. A capture leaves the pose of the last time it drew,
|
|
6297
|
+
// so the samples end on 0 again: the canvas is left at rest, as found.
|
|
6298
|
+
if (result && result.success && ['mp4', 'webm', 'gif', 'apng'].indexOf(format) !== -1
|
|
6299
|
+
&& result.fidelity && result.fidelity.checked && result.fidelity.checked.animated === false
|
|
6300
|
+
&& typeof app.captureFramesAt === 'function') {
|
|
6301
|
+
try {
|
|
6302
|
+
const __mt = [0, 0.17, 0.43, 0.71].map((k) => Math.round(k * ${videoDuration} * 1000) / 1000);
|
|
6303
|
+
const __mcap = typeof app.captureFramesAtAsync === 'function' ? app.captureFramesAtAsync.bind(app) : app.captureFramesAt.bind(app);
|
|
6304
|
+
const __mf = await __mcap(__mt.concat([0]), { seed: 0, capture: (c) => (typeof app.captureFrameDataURL === 'function' ? app.captureFrameDataURL() : (c && c.toDataURL ? c.toDataURL() : '')) });
|
|
6305
|
+
const __mu = new Set((__mf || []).filter((u) => typeof u === 'string' && u.length > 0));
|
|
6306
|
+
if (__mu.size > 1) {
|
|
6307
|
+
result.fidelity.checked.animated = true;
|
|
6308
|
+
result.fidelity.checked.motion = 'frames differ across the export (sampled at ' + __mt.join(', ') + ' s): the motion comes from relations, generators or the camera, not keyframe tracks';
|
|
6309
|
+
}
|
|
6310
|
+
} catch (e) { /* the studio's answer stands */ }
|
|
6311
|
+
}
|
|
6130
6312
|
if (result && result.success && __lint && __lint.length && !result.lintWarnings) {
|
|
6131
6313
|
result.lintWarnings = __lint;
|
|
6132
6314
|
result.fidelity = result.fidelity || { warnings: [] };
|
|
@@ -6416,7 +6598,7 @@ ${(() => {
|
|
|
6416
6598
|
if (__bs.stale) {
|
|
6417
6599
|
result.stale = true;
|
|
6418
6600
|
result.fidelity = result.fidelity || { warnings: [] };
|
|
6419
|
-
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.' }]);
|
|
6420
6602
|
}
|
|
6421
6603
|
}
|
|
6422
6604
|
} catch (_) { /* older studio */ }
|
|
@@ -6428,7 +6610,7 @@ ${(() => {
|
|
|
6428
6610
|
if (__rl && __rl.enabled) {
|
|
6429
6611
|
result.fidelity = result.fidelity || { warnings: [] };
|
|
6430
6612
|
if (['png', 'jpg', 'webp', 'pdf', 'mp4', 'webm'].indexOf(${JSON.stringify(format)}) === -1) {
|
|
6431
|
-
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.' }]);
|
|
6432
6614
|
} else {
|
|
6433
6615
|
result.relight = { applied: true, lights: (__rl.lights || []).length };
|
|
6434
6616
|
}
|
|
@@ -6659,7 +6841,7 @@ ${(() => {
|
|
|
6659
6841
|
const validated = AnimateLetterCollageInputSchema.parse(input);
|
|
6660
6842
|
const { collageId, animationType, staggerDelay = 0.1, animationSpeed = 1, } = validated;
|
|
6661
6843
|
return `
|
|
6662
|
-
// Animate letter collage: ${collageId}
|
|
6844
|
+
// Animate letter collage: ${oneLine(collageId)}
|
|
6663
6845
|
(function() {
|
|
6664
6846
|
const collageId = ${JSON.stringify(collageId)};
|
|
6665
6847
|
const animationType = ${JSON.stringify(animationType)};
|
|
@@ -6756,10 +6938,10 @@ ${(() => {
|
|
|
6756
6938
|
}
|
|
6757
6939
|
|
|
6758
6940
|
try {
|
|
6759
|
-
const result = await app.mapSystem.loadMap(
|
|
6941
|
+
const result = await app.mapSystem.loadMap(${jsLiteral(mapId)}, ${optionsStr});
|
|
6760
6942
|
return {
|
|
6761
6943
|
success: true,
|
|
6762
|
-
mapId: result.mapId ||
|
|
6944
|
+
mapId: result.mapId || ${jsLiteral(mapId)},
|
|
6763
6945
|
// regionPaths is a Map: .length was undefined, so every load reported
|
|
6764
6946
|
// regions: 0 (gate D3). size, with the feature count as a fallback.
|
|
6765
6947
|
regions: (result.regions && (result.regions.size ?? result.regions.length)) || (result.geoData && result.geoData.features ? result.geoData.features.length : 0),
|
|
@@ -7176,7 +7358,7 @@ ${(() => {
|
|
|
7176
7358
|
}
|
|
7177
7359
|
|
|
7178
7360
|
try {
|
|
7179
|
-
const result = await app.mapSystem.importCustomMap(
|
|
7361
|
+
const result = await app.mapSystem.importCustomMap(${jsLiteral(validated.url)}, ${optionsStr});
|
|
7180
7362
|
// A summary, not the engine's return: that carries the Paper group and
|
|
7181
7363
|
// the region Map, which serialised into a raw Paper.js dump (gate N1).
|
|
7182
7364
|
const __r = result && result.regions;
|
|
@@ -7294,7 +7476,7 @@ ${(() => {
|
|
|
7294
7476
|
loop: ${validated.loop !== false},
|
|
7295
7477
|
colors: ${colorsStr},${validated.waveSpeed !== undefined ? `
|
|
7296
7478
|
waveSpeed: ${validated.waveSpeed},` : ''}
|
|
7297
|
-
waveDirection:
|
|
7479
|
+
waveDirection: ${jsLiteral(validated.waveDirection || 'horizontal')}
|
|
7298
7480
|
});
|
|
7299
7481
|
return { success: true, animatedRegions: result?.animatedRegions || [], totalRegions: result?.totalRegions || 0 };
|
|
7300
7482
|
} catch (error) {
|
|
@@ -7366,7 +7548,7 @@ ${(() => {
|
|
|
7366
7548
|
includeSelected: ${validated.includeSelected !== false},
|
|
7367
7549
|
includeColors: ${validated.includeColors !== false},
|
|
7368
7550
|
download: ${validated.download === true},
|
|
7369
|
-
filename:
|
|
7551
|
+
filename: ${jsLiteral(validated.filename || 'map-regions.csv')}
|
|
7370
7552
|
});
|
|
7371
7553
|
return { success: true, csv: result?.csv || '', regionCount: result?.regionCount || 0 };
|
|
7372
7554
|
} catch (error) {
|
|
@@ -7623,7 +7805,7 @@ ${(() => {
|
|
|
7623
7805
|
|
|
7624
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)})));
|
|
7625
7807
|
if (!item) {
|
|
7626
|
-
return { success: false, error: 'Item ${itemId} not found' };
|
|
7808
|
+
return { success: false, error: 'Item ' + ${jsLiteral(itemId)} + ' not found' };
|
|
7627
7809
|
}
|
|
7628
7810
|
|
|
7629
7811
|
try {
|
|
@@ -7652,7 +7834,7 @@ ${(() => {
|
|
|
7652
7834
|
|
|
7653
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)})));
|
|
7654
7836
|
if (!item) {
|
|
7655
|
-
return { success: false, error: 'Item ${itemId} not found' };
|
|
7837
|
+
return { success: false, error: 'Item ' + ${jsLiteral(itemId)} + ' not found' };
|
|
7656
7838
|
}
|
|
7657
7839
|
|
|
7658
7840
|
try {
|
|
@@ -8025,10 +8207,10 @@ ${(() => {
|
|
|
8025
8207
|
const paramsStr = validated.params ? JSON.stringify(validated.params) : '{}';
|
|
8026
8208
|
const templatesStr = validated.templates ? JSON.stringify(validated.templates) : '[]';
|
|
8027
8209
|
return `
|
|
8028
|
-
// Register custom relation: ${validated.name}
|
|
8210
|
+
// Register custom relation: ${oneLine(validated.name)}
|
|
8029
8211
|
(function() {
|
|
8030
8212
|
try {
|
|
8031
|
-
app.registerRelationRule(
|
|
8213
|
+
app.registerRelationRule(${jsLiteral(validated.name)}, {
|
|
8032
8214
|
description: ${JSON.stringify(validated.description || '')},
|
|
8033
8215
|
params: ${paramsStr},
|
|
8034
8216
|
compute: (ctx) => {
|
|
@@ -8042,7 +8224,11 @@ ${(() => {
|
|
|
8042
8224
|
continuous: ${validated.continuous !== false},
|
|
8043
8225
|
priority: ${validated.priority || 0}
|
|
8044
8226
|
});
|
|
8045
|
-
|
|
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' };
|
|
8046
8232
|
} catch (error) {
|
|
8047
8233
|
return { success: false, error: error.message };
|
|
8048
8234
|
}
|
|
@@ -8073,7 +8259,7 @@ ${(() => {
|
|
|
8073
8259
|
// __ppValue with the scene lint beside it, and a throw comes back as { step, itemId, reason }, never a bare
|
|
8074
8260
|
// message (and never "undefined" for a thrown non-Error).
|
|
8075
8261
|
return `
|
|
8076
|
-
// ${description}
|
|
8262
|
+
// ${oneLine(description)}
|
|
8077
8263
|
(async function() {
|
|
8078
8264
|
// THE SCENE LINT AFTER THE CODE (FxTool lintScene().warnings): what the scene will show wrong without anything
|
|
8079
8265
|
// failing (a centred title that runs out of the frame). Carried out under __ppLint; the server moves it beside the
|
|
@@ -8129,21 +8315,21 @@ ${(() => {
|
|
|
8129
8315
|
// Per-preset options. Custom keyframes are an animated-mask extension —
|
|
8130
8316
|
// when supplied, fall through to maskingSystem.applyCustomMask instead.
|
|
8131
8317
|
return `
|
|
8132
|
-
// Apply animated mask to ${validated.itemId}
|
|
8318
|
+
// Apply animated mask to ${oneLine(validated.itemId)}
|
|
8133
8319
|
(function() {
|
|
8134
8320
|
if (!app.applyAnimatedMask) {
|
|
8135
8321
|
return { success: false, error: 'Mask system not available' };
|
|
8136
8322
|
}
|
|
8137
|
-
const item = app.getItemById(
|
|
8138
|
-
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)} };
|
|
8139
8325
|
|
|
8140
8326
|
try {
|
|
8141
8327
|
const mergedOpts = Object.assign({}, ${optionsStr}, ${maskOptionsStr});
|
|
8142
8328
|
let maskedGroup;
|
|
8143
8329
|
if (${keyframesStr} && app.maskingSystem && app.maskingSystem.applyCustomMask) {
|
|
8144
|
-
maskedGroup = app.maskingSystem.applyCustomMask(item,
|
|
8330
|
+
maskedGroup = app.maskingSystem.applyCustomMask(item, ${jsLiteral(customShape)}, ${keyframesStr}, mergedOpts);
|
|
8145
8331
|
} else {
|
|
8146
|
-
maskedGroup = app.applyAnimatedMask(item,
|
|
8332
|
+
maskedGroup = app.applyAnimatedMask(item, ${jsLiteral(preset)}, mergedOpts);
|
|
8147
8333
|
}
|
|
8148
8334
|
// A mask with an unknown ANIMATION preset still masks — the item just never
|
|
8149
8335
|
// animates — so !!maskedGroup is true either way. The engine records which.
|
|
@@ -8151,13 +8337,13 @@ ${(() => {
|
|
|
8151
8337
|
if (rej) {
|
|
8152
8338
|
return {
|
|
8153
8339
|
success: false,
|
|
8154
|
-
itemId:
|
|
8340
|
+
itemId: ${jsLiteral(validated.itemId)},
|
|
8155
8341
|
error: 'Masked, but the animation preset "' + rej.requested + '" is unknown — it will NOT animate.',
|
|
8156
8342
|
requested: rej.requested,
|
|
8157
8343
|
known: rej.known,
|
|
8158
8344
|
};
|
|
8159
8345
|
}
|
|
8160
|
-
return { success: !!maskedGroup, itemId:
|
|
8346
|
+
return { success: !!maskedGroup, itemId: ${jsLiteral(validated.itemId)}, preset: ${jsLiteral(preset)} };
|
|
8161
8347
|
} catch (error) {
|
|
8162
8348
|
return { success: false, error: error.message };
|
|
8163
8349
|
}
|
|
@@ -8169,17 +8355,17 @@ ${(() => {
|
|
|
8169
8355
|
const keyframesStr = JSON.stringify(validated.keyframes);
|
|
8170
8356
|
const maskOptionsStr = validated.maskOptions ? JSON.stringify(validated.maskOptions) : '{}';
|
|
8171
8357
|
return `
|
|
8172
|
-
// Apply custom mask to ${validated.itemId}
|
|
8358
|
+
// Apply custom mask to ${oneLine(validated.itemId)}
|
|
8173
8359
|
(function() {
|
|
8174
8360
|
if (!app.maskingSystem || !app.maskingSystem.applyCustomMask) {
|
|
8175
8361
|
return { success: false, error: 'Mask system not available' };
|
|
8176
8362
|
}
|
|
8177
|
-
const item = app.getItemById(
|
|
8178
|
-
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)} };
|
|
8179
8365
|
|
|
8180
8366
|
try {
|
|
8181
|
-
const maskedGroup = app.maskingSystem.applyCustomMask(item,
|
|
8182
|
-
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)} };
|
|
8183
8369
|
} catch (error) {
|
|
8184
8370
|
return { success: false, error: error.message };
|
|
8185
8371
|
}
|
|
@@ -8189,20 +8375,20 @@ ${(() => {
|
|
|
8189
8375
|
generateRemoveMask(input) {
|
|
8190
8376
|
const validated = RemoveMaskInputSchema.parse(input);
|
|
8191
8377
|
return `
|
|
8192
|
-
// Remove mask from ${validated.itemId}
|
|
8378
|
+
// Remove mask from ${oneLine(validated.itemId)}
|
|
8193
8379
|
(function() {
|
|
8194
8380
|
if (!app.removeMask) {
|
|
8195
8381
|
return { success: false, error: 'Mask system not available' };
|
|
8196
8382
|
}
|
|
8197
8383
|
// The item id may point at either the masked group OR the original item;
|
|
8198
8384
|
// app.removeMask expects the masked group, so resolve and walk up if needed.
|
|
8199
|
-
const item = app.getItemById(
|
|
8200
|
-
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)} };
|
|
8201
8387
|
const maskedGroup = (item.data && item.data.isMaskedGroup) ? item : (item.parent && item.parent.data && item.parent.data.isMaskedGroup ? item.parent : item);
|
|
8202
8388
|
|
|
8203
8389
|
try {
|
|
8204
8390
|
app.removeMask(maskedGroup);
|
|
8205
|
-
return { success: true, itemId:
|
|
8391
|
+
return { success: true, itemId: ${jsLiteral(validated.itemId)}, message: 'Mask removed' };
|
|
8206
8392
|
} catch (error) {
|
|
8207
8393
|
return { success: false, error: error.message };
|
|
8208
8394
|
}
|
|
@@ -8231,7 +8417,7 @@ ${(() => {
|
|
|
8231
8417
|
return { success: false, error: 'app.listAnimatableProperties unavailable — update the studio. The keyframe tool lists the properties it accepts in its own description meanwhile.' };
|
|
8232
8418
|
}
|
|
8233
8419
|
const item = app.getItemById(${JSON.stringify(itemId)});
|
|
8234
|
-
if (!item) { return { success: false, error: 'no item ${itemId}
|
|
8420
|
+
if (!item) { return { success: false, error: 'no item ' + ${jsLiteral(itemId)} }; }
|
|
8235
8421
|
const r = app.listAnimatableProperties(item);
|
|
8236
8422
|
return { success: true, ...r };
|
|
8237
8423
|
})();`.trim();
|
|
@@ -8329,7 +8515,7 @@ ${(() => {
|
|
|
8329
8515
|
generateApplyTemplate(input) {
|
|
8330
8516
|
const { templateId, category, listOnly } = input;
|
|
8331
8517
|
if (listOnly || !templateId) {
|
|
8332
|
-
const categoryFilter = category ?
|
|
8518
|
+
const categoryFilter = category ? jsLiteral(category) : 'null';
|
|
8333
8519
|
return `
|
|
8334
8520
|
// List available templates
|
|
8335
8521
|
(async function() {
|
|
@@ -8357,7 +8543,7 @@ ${(() => {
|
|
|
8357
8543
|
`.trim();
|
|
8358
8544
|
}
|
|
8359
8545
|
return `
|
|
8360
|
-
// Apply template: ${templateId}
|
|
8546
|
+
// Apply template: ${oneLine(templateId)}
|
|
8361
8547
|
(async function() {
|
|
8362
8548
|
if (!app.templateManager) {
|
|
8363
8549
|
return { error: 'Template manager not available. Make sure PinePaper Studio is loaded.' };
|
|
@@ -8404,7 +8590,7 @@ ${(() => {
|
|
|
8404
8590
|
let fidelity = {};
|
|
8405
8591
|
try {
|
|
8406
8592
|
const tpl = (app.templateManager.getAllTemplates() || [])
|
|
8407
|
-
.find(function(t) { return t && (t.id ===
|
|
8593
|
+
.find(function(t) { return t && (t.id === ${jsLiteral(templateId)} || t.templateId === ${jsLiteral(templateId)}); });
|
|
8408
8594
|
const items = tpl && tpl.data && Array.isArray(tpl.data.items) ? tpl.data.items : null;
|
|
8409
8595
|
const declared = items ? items.filter(function(i) { return i && i.type === 'group' && i.paperJSON; }).length : 0;
|
|
8410
8596
|
if (declared > 0 && app.itemRegistry && typeof app.itemRegistry.getAll === 'function') {
|
|
@@ -8425,7 +8611,7 @@ ${(() => {
|
|
|
8425
8611
|
}
|
|
8426
8612
|
} catch (e) { /* a check that cannot run must not fail the load */ }
|
|
8427
8613
|
|
|
8428
|
-
return { success: true, templateId:
|
|
8614
|
+
return { success: true, templateId: ${jsLiteral(templateId)}, message: 'Template applied successfully. Canvas has been replaced with template content.', ...fidelity };
|
|
8429
8615
|
} catch (e) {
|
|
8430
8616
|
return { error: 'Failed to apply template: ' + e.message };
|
|
8431
8617
|
}
|
|
@@ -8475,7 +8661,7 @@ ${(() => {
|
|
|
8475
8661
|
// The bytes then go in as a DATA URL, which img.src loads without touching
|
|
8476
8662
|
// the network again — so a host that allows fetch but blocks the image
|
|
8477
8663
|
// load (CORS on <img>, a tainted-canvas refusal) now works too.
|
|
8478
|
-
let src =
|
|
8664
|
+
let src = ${jsLiteral(url)};
|
|
8479
8665
|
// A STAGED KEY, not the bytes. The handler writes anything over ~64KB to
|
|
8480
8666
|
// window.__ppStage and passes the key instead, because the governor's
|
|
8481
8667
|
// loop-guard transform bails on a code string that large and takes the
|
|
@@ -8537,7 +8723,7 @@ ${mask ? ` // A MASK REPLACES THE RASTER (round 8 DD, 4.10). applyMask builds
|
|
|
8537
8723
|
// refused instead of answered with the raster's bounds.
|
|
8538
8724
|
const masked = app.imageTools.applyMask(raster, ${JSON.stringify(mask)});
|
|
8539
8725
|
if (!masked) {
|
|
8540
|
-
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 + '.' };
|
|
8541
8727
|
}
|
|
8542
8728
|
if (app.itemRegistry && typeof app.itemRegistry.rebind === 'function') app.itemRegistry.rebind(itemId, masked);
|
|
8543
8729
|
placed = masked;
|
|
@@ -8564,7 +8750,7 @@ ${mask ? ` // A MASK REPLACES THE RASTER (round 8 DD, 4.10). applyMask builds
|
|
|
8564
8750
|
success: true,
|
|
8565
8751
|
itemId: itemId,
|
|
8566
8752
|
message: 'Image imported and placed on canvas.',${name ? `\n name: ${JSON.stringify(name)},` : ''}
|
|
8567
|
-
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' : ''},
|
|
8568
8754
|
// Stored smaller than its source (the studio's cap): said, with the
|
|
8569
8755
|
// way to keep every pixel. The studio reports native size where it can.
|
|
8570
8756
|
...((entry && entry.nativeWidth && entry.width && entry.nativeWidth > entry.width)
|
|
@@ -8658,7 +8844,7 @@ ${mask ? ` // A MASK REPLACES THE RASTER (round 8 DD, 4.10). applyMask builds
|
|
|
8658
8844
|
(function() {
|
|
8659
8845
|
${guard}
|
|
8660
8846
|
const scene = app.sceneManager.getScene(${JSON.stringify(input.sceneId)});
|
|
8661
|
-
if (!scene) return { error: 'Scene not found: ${input.sceneId}
|
|
8847
|
+
if (!scene) return { error: 'Scene not found: ' + ${jsLiteral(input.sceneId)} };
|
|
8662
8848
|
return { success: true, action: 'info', scene: scene };
|
|
8663
8849
|
})();
|
|
8664
8850
|
`.trim();
|
|
@@ -9122,7 +9308,7 @@ ${input.cutSound !== undefined ? ` const __cuts = (__cr && __cr.cuts) || (app.s
|
|
|
9122
9308
|
selected.push(id);
|
|
9123
9309
|
}
|
|
9124
9310
|
}
|
|
9125
|
-
return { success: true, action: 'select', mode:
|
|
9311
|
+
return { success: true, action: 'select', mode: ${jsLiteral(mode)}, selected };
|
|
9126
9312
|
})();`.trim();
|
|
9127
9313
|
}
|
|
9128
9314
|
case 'select_all':
|
|
@@ -9179,7 +9365,7 @@ ${input.cutSound !== undefined ? ` const __cuts = (__cr && __cr.cuts) || (app.s
|
|
|
9179
9365
|
*/
|
|
9180
9366
|
_facadeCall(method, args, label) {
|
|
9181
9367
|
return `
|
|
9182
|
-
// ${label}
|
|
9368
|
+
// ${oneLine(label)}
|
|
9183
9369
|
(async function() {
|
|
9184
9370
|
if (typeof app.${method} !== 'function') {
|
|
9185
9371
|
return { success: false, error: 'app.${method}() unavailable — update PinePaper Studio to a build that has it.' };
|
|
@@ -9201,7 +9387,7 @@ ${input.cutSound !== undefined ? ` const __cuts = (__cr && __cr.cuts) || (app.s
|
|
|
9201
9387
|
// studios, and a brand must not cross between them.
|
|
9202
9388
|
if (input.action === 'get' || input.action === 'clear') {
|
|
9203
9389
|
return `
|
|
9204
|
-
// Brand kit: ${input.action} the active brand
|
|
9390
|
+
// Brand kit: ${oneLine(input.action)} the active brand
|
|
9205
9391
|
(function() {
|
|
9206
9392
|
const b = (typeof window !== 'undefined' && window.__ppActiveBrand) || null;
|
|
9207
9393
|
${input.action === 'clear' ? 'if (typeof window !== "undefined") window.__ppActiveBrand = null;' : ''}
|
|
@@ -9458,7 +9644,7 @@ ${loads}
|
|
|
9458
9644
|
__src = raster.url;
|
|
9459
9645
|
}`;
|
|
9460
9646
|
const call = (method, args, label) => `
|
|
9461
|
-
// ${label}
|
|
9647
|
+
// ${oneLine(label)}
|
|
9462
9648
|
(async function() {
|
|
9463
9649
|
if (typeof app.${method} !== 'function') {
|
|
9464
9650
|
return { success: false, error: 'app.${method}() unavailable — update PinePaper Studio to a build that has it.' };
|
|
@@ -9602,7 +9788,7 @@ ${resolveSource}
|
|
|
9602
9788
|
case 'fit': {
|
|
9603
9789
|
const mode = input.mode || 'contain';
|
|
9604
9790
|
return `
|
|
9605
|
-
// Fit item to the export frame (${mode})
|
|
9791
|
+
// Fit item to the export frame (${oneLine(mode)})
|
|
9606
9792
|
(function() {
|
|
9607
9793
|
// Route through the agent facade, not app.fitToFrame directly: an agent that
|
|
9608
9794
|
// just called upload_video holds a MEDIA id (vraster_…), which is a different
|
|
@@ -9645,7 +9831,7 @@ ${resolveSource}
|
|
|
9645
9831
|
case 'flip': {
|
|
9646
9832
|
const dir = input.direction || 'horizontal';
|
|
9647
9833
|
return `
|
|
9648
|
-
// Flip item ${dir}ly
|
|
9834
|
+
// Flip item ${oneLine(dir)}ly
|
|
9649
9835
|
(function() {
|
|
9650
9836
|
const entry = app.itemRegistry.get(${ID});
|
|
9651
9837
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${ID} };
|
|
@@ -9666,7 +9852,7 @@ ${resolveSource}
|
|
|
9666
9852
|
if (needsSibling) {
|
|
9667
9853
|
const siblingExpr = order === 'moveUp' ? 'entry.item.nextSibling' : 'entry.item.previousSibling';
|
|
9668
9854
|
return `
|
|
9669
|
-
// Reorder item: ${order}
|
|
9855
|
+
// Reorder item: ${oneLine(order)}
|
|
9670
9856
|
(function() {
|
|
9671
9857
|
const entry = app.itemRegistry.get(${ID});
|
|
9672
9858
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${ID} };
|
|
@@ -9676,7 +9862,7 @@ ${resolveSource}
|
|
|
9676
9862
|
})();`.trim();
|
|
9677
9863
|
}
|
|
9678
9864
|
return `
|
|
9679
|
-
// Reorder item: ${order}
|
|
9865
|
+
// Reorder item: ${oneLine(order)}
|
|
9680
9866
|
(function() {
|
|
9681
9867
|
const entry = app.itemRegistry.get(${ID});
|
|
9682
9868
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${ID} };
|
|
@@ -9766,7 +9952,7 @@ ${resolve}
|
|
|
9766
9952
|
// otherwise; a falsy answer is a refusal it did not raise.
|
|
9767
9953
|
if (r === false || r === null) {
|
|
9768
9954
|
return { success: false, action: 'apply', itemId: ${JSON.stringify(input.itemId)},
|
|
9769
|
-
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.' };
|
|
9770
9956
|
}
|
|
9771
9957
|
return { success: true, action: 'apply', itemId: ${JSON.stringify(input.itemId)}, filter: ${JSON.stringify(input.filterName || '')} };
|
|
9772
9958
|
} catch (e) {
|
|
@@ -10121,7 +10307,7 @@ ${resolve}
|
|
|
10121
10307
|
// Get item details by ID
|
|
10122
10308
|
(function() {
|
|
10123
10309
|
const entry = app.itemRegistry.get(${JSON.stringify(input.itemId || '')});
|
|
10124
|
-
if (!entry || !entry.item) return { error: 'Item not found: ${input.itemId}
|
|
10310
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${jsLiteral(input.itemId)} };
|
|
10125
10311
|
const item = entry.item;
|
|
10126
10312
|
return {
|
|
10127
10313
|
success: true, action: 'get_by_id',
|
|
@@ -10421,8 +10607,8 @@ ${resolve}
|
|
|
10421
10607
|
${guard}
|
|
10422
10608
|
const entry = app.itemRegistry.get(${itemIdStr});
|
|
10423
10609
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
10424
|
-
app.deformPresets.apply(entry.item,
|
|
10425
|
-
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)} };
|
|
10426
10612
|
})();`.trim();
|
|
10427
10613
|
}
|
|
10428
10614
|
case 'trigger': {
|
|
@@ -10442,8 +10628,8 @@ ${resolve}
|
|
|
10442
10628
|
${guard}
|
|
10443
10629
|
const entry = app.itemRegistry.get(${itemIdStr});
|
|
10444
10630
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
10445
|
-
app.deformPresets.trigger(entry.item,
|
|
10446
|
-
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)} };
|
|
10447
10633
|
})();`.trim();
|
|
10448
10634
|
}
|
|
10449
10635
|
case 'remove': {
|
|
@@ -10696,8 +10882,8 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
10696
10882
|
// Export sprite sheet
|
|
10697
10883
|
(async function() {
|
|
10698
10884
|
${guard}
|
|
10699
|
-
const result = await app.exportSpriteSheet(${JSON.stringify(input.spriteSheetId || '')}, { format:
|
|
10700
|
-
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')} };
|
|
10701
10887
|
})();`.trim();
|
|
10702
10888
|
default:
|
|
10703
10889
|
return `(function() { return { error: 'Unknown sprite sheet action: ${input.action}' }; })();`;
|
|
@@ -10718,8 +10904,8 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
10718
10904
|
${guard}
|
|
10719
10905
|
const entry = app.itemRegistry.get(${itemIdStr});
|
|
10720
10906
|
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
10721
|
-
const behaviorId = app.interactionSystem.addContinuousBehavior(entry.item,
|
|
10722
|
-
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 };
|
|
10723
10909
|
})();`.trim();
|
|
10724
10910
|
}
|
|
10725
10911
|
case 'remove_behavior': {
|
|
@@ -10776,8 +10962,8 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
10776
10962
|
// Trigger interaction action
|
|
10777
10963
|
(function() {
|
|
10778
10964
|
${guard}
|
|
10779
|
-
app.interactionSystem.executeAction(
|
|
10780
|
-
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)} };
|
|
10781
10967
|
})();`.trim();
|
|
10782
10968
|
}
|
|
10783
10969
|
case 'get_state':
|
|
@@ -11204,14 +11390,14 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11204
11390
|
}[input.action];
|
|
11205
11391
|
const id = JSON.stringify(input.itemId);
|
|
11206
11392
|
return `
|
|
11207
|
-
// Arrange ${input.itemId} → ${input.action}
|
|
11393
|
+
// Arrange ${oneLine(input.itemId)} → ${oneLine(input.action)}
|
|
11208
11394
|
(function() {
|
|
11209
11395
|
const item = app.getItemById(${id});
|
|
11210
11396
|
if (!item) return { success: false, error: 'Item not found: ' + ${id} };
|
|
11211
11397
|
if (typeof app.${method} !== 'function') return { success: false, error: 'app.${method} unavailable — update FxTool' };
|
|
11212
11398
|
app.select(${id});
|
|
11213
11399
|
app.${method}();
|
|
11214
|
-
return { success: true, itemId: ${id}, action:
|
|
11400
|
+
return { success: true, itemId: ${id}, action: ${jsLiteral(input.action)} };
|
|
11215
11401
|
})();`.trim();
|
|
11216
11402
|
}
|
|
11217
11403
|
/**
|
|
@@ -11244,7 +11430,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11244
11430
|
...(input.queries !== undefined ? { queries: input.queries } : {}),
|
|
11245
11431
|
});
|
|
11246
11432
|
return `
|
|
11247
|
-
// 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)' : '')}
|
|
11248
11434
|
(async function() {
|
|
11249
11435
|
if (typeof app.detectObjects !== 'function') {
|
|
11250
11436
|
return { success: false, error: 'app.detectObjects unavailable — update FxTool to a build that exposes on-device object detection on app' };
|
|
@@ -11274,7 +11460,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11274
11460
|
...(input.id !== undefined ? { id: input.id } : {}),
|
|
11275
11461
|
};
|
|
11276
11462
|
return `
|
|
11277
|
-
// Styled scene${input.style ? ` (${input.style.replace(/[\r\n]/g, ' ')})` : ''}
|
|
11463
|
+
// Styled scene${input.style ? ` (${oneLine(input.style.replace(/[\r\n]/g, ' '))})` : ''}
|
|
11278
11464
|
(async function() {
|
|
11279
11465
|
if (typeof app.styledScene !== 'function') { return { success: false, error: 'app.styledScene unavailable — update FxTool' }; }
|
|
11280
11466
|
const r = await app.styledScene(${JSON.stringify(input.spec ?? {})}, ${JSON.stringify(opts)});
|
|
@@ -11294,7 +11480,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11294
11480
|
...(input.threshold !== undefined ? { threshold: input.threshold } : {}),
|
|
11295
11481
|
});
|
|
11296
11482
|
return `
|
|
11297
|
-
// Extract object${input.label ? `
|
|
11483
|
+
// Extract object${input.label ? ` ${jsLiteral(input.label)}` : ''} from the image
|
|
11298
11484
|
(async function() {
|
|
11299
11485
|
if (typeof app.extractObject !== 'function') {
|
|
11300
11486
|
return { success: false, error: 'app.extractObject unavailable — update FxTool to a build that exposes on-device object detection on app' };
|
|
@@ -11384,7 +11570,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11384
11570
|
if (input.action === 'break_apart') {
|
|
11385
11571
|
const idExpr = JSON.stringify(input.itemId || '');
|
|
11386
11572
|
return `
|
|
11387
|
-
// Break apart ${input.itemId || ''} into movable parts
|
|
11573
|
+
// Break apart ${oneLine(input.itemId || '')} into movable parts
|
|
11388
11574
|
(function() {
|
|
11389
11575
|
if (typeof app.breakApart !== 'function') return { success: false, error: 'app.breakApart unavailable — update FxTool to a build with SVG/group segmentation' };
|
|
11390
11576
|
const target = app.getItemById(${idExpr});
|
|
@@ -11399,7 +11585,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11399
11585
|
if (input.action === 'ungroup') {
|
|
11400
11586
|
const idExpr = JSON.stringify(input.groupId || '');
|
|
11401
11587
|
return `
|
|
11402
|
-
// Ungroup ${input.groupId || ''}
|
|
11588
|
+
// Ungroup ${oneLine(input.groupId || '')}
|
|
11403
11589
|
(function() {
|
|
11404
11590
|
if (!app.groupManager) return { success: false, error: 'GroupManager not available — update FxTool' };
|
|
11405
11591
|
const gid = ${idExpr};
|
|
@@ -11413,7 +11599,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11413
11599
|
const idsArrayExpr = JSON.stringify(input.itemIds ?? []);
|
|
11414
11600
|
const nameExpr = input.groupName !== undefined ? JSON.stringify(input.groupName) : 'undefined';
|
|
11415
11601
|
return `
|
|
11416
|
-
// Group ${(input.itemIds || []).length} items${input.groupName ? ` as
|
|
11602
|
+
// Group ${oneLine((input.itemIds || []).length)} items${input.groupName ? ` as ${jsLiteral(input.groupName)}` : ''}
|
|
11417
11603
|
(function() {${this.groupCodeBody(idsArrayExpr, nameExpr)}
|
|
11418
11604
|
})();`.trim();
|
|
11419
11605
|
}
|
|
@@ -11463,7 +11649,7 @@ if (!app.spriteSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
|
11463
11649
|
const duration = input.duration ?? 5;
|
|
11464
11650
|
const n = Math.min(1200, Math.max(3, Math.round(duration * fps)));
|
|
11465
11651
|
return `
|
|
11466
|
-
// Accessibility measurements (${checks.join(', ')})
|
|
11652
|
+
// Accessibility measurements (${oneLine(checks.join(', '))})
|
|
11467
11653
|
(async function() {
|
|
11468
11654
|
const out = { success: true, fps: ${fps}, duration: ${duration} };
|
|
11469
11655
|
const rgba = function(c) {
|
|
@@ -11620,7 +11806,7 @@ ${checks.includes('contrast') ? ` // SAMPLED OVER TIME (round 12, 12.9): text i
|
|
|
11620
11806
|
const mime = sheet.format === 'png' ? 'image/png' : 'image/jpeg';
|
|
11621
11807
|
const S = (v) => JSON.stringify(v);
|
|
11622
11808
|
return `
|
|
11623
|
-
// Contact sheet of ${n} frame(s)
|
|
11809
|
+
// Contact sheet of ${oneLine(n)} frame(s)
|
|
11624
11810
|
(async function() {
|
|
11625
11811
|
if (typeof app.captureFramesAt !== 'function') {
|
|
11626
11812
|
return { success: false, error: 'app.captureFramesAt unavailable — update FxTool to a build with the deterministic capture entrypoint' };
|
|
@@ -11696,7 +11882,7 @@ ${checks.includes('contrast') ? ` // SAMPLED OVER TIME (round 12, 12.9): text i
|
|
|
11696
11882
|
// tool call that screenshots next wants that to have happened.
|
|
11697
11883
|
const engineOptsJson = JSON.stringify({ canvas: input.canvas, defaultGeometry: input.defaultGeometry, awaitImages: true });
|
|
11698
11884
|
return `
|
|
11699
|
-
// 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)
|
|
11700
11886
|
(async function() {
|
|
11701
11887
|
// Engine door first: the studio's own OntologyCompiler reads every node facet
|
|
11702
11888
|
// (geometry, paint, text, the keyframe track, masks) and builds through the
|
|
@@ -11781,10 +11967,27 @@ ${checks.includes('contrast') ? ` // SAMPLED OVER TIME (round 12, 12.9): text i
|
|
|
11781
11967
|
return { success: false, error: 'app.lintScene unavailable — update FxTool to a build with the relational linter (S12-E2)' };
|
|
11782
11968
|
}
|
|
11783
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) : [];
|
|
11784
11985
|
// warnings: what the engine's own rules found (TEXT_CLIPS_FRAME: a centre-anchored text running past the
|
|
11785
11986
|
// frame edge). Passed through when the studio reports them; an older studio has none to pass.
|
|
11786
11987
|
return { success: true, density: result.density, suggestions: result.suggestions,
|
|
11787
|
-
...(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 } : {}) };
|
|
11788
11991
|
})();`.trim();
|
|
11789
11992
|
}
|
|
11790
11993
|
/**
|
|
@@ -11815,11 +12018,11 @@ ${checks.includes('contrast') ? ` // SAMPLED OVER TIME (round 12, 12.9): text i
|
|
|
11815
12018
|
if (u.indexOf('data:') !== 0) return u;
|
|
11816
12019
|
const comma = u.indexOf(',');
|
|
11817
12020
|
const meta = u.slice(5, comma);
|
|
11818
|
-
const mime = meta.split(';')[0] ||
|
|
12021
|
+
const mime = meta.split(';')[0] || ${jsLiteral(kind === 'audio' ? 'audio/mpeg' : 'video/mp4')};
|
|
11819
12022
|
const body = u.slice(comma + 1);
|
|
11820
12023
|
const bin = /;base64/i.test(meta) ? atob(body) : decodeURIComponent(body);
|
|
11821
12024
|
const bytes = Uint8Array.from(bin, function(c) { return c.charCodeAt(0); });
|
|
11822
|
-
const ext = (mime.split('/')[1] ||
|
|
12025
|
+
const ext = (mime.split('/')[1] || ${jsLiteral(kind === 'audio' ? 'mp3' : 'mp4')}).replace(/[^a-z0-9]/gi, '');
|
|
11823
12026
|
return new File([bytes], '${kind}.' + ext, { type: mime });
|
|
11824
12027
|
})(${JSON.stringify(url ?? '')})`;
|
|
11825
12028
|
// TWO ID SPACES, AND THE CALLER HOLDS THE WRONG ONE.
|
|
@@ -11928,7 +12131,7 @@ ${input.timeOffset !== undefined ? ` // Moving the clip is the 4th argument, wh
|
|
|
11928
12131
|
// refusals come back as {ok:false, reason} and are errors here.
|
|
11929
12132
|
case 'split':
|
|
11930
12133
|
return `
|
|
11931
|
-
// Split a media clip at canvas time ${input.at}
|
|
12134
|
+
// Split a media clip at canvas time ${oneLine(input.at)}
|
|
11932
12135
|
(async function() {
|
|
11933
12136
|
${guard}
|
|
11934
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.' }; }
|
|
@@ -11938,7 +12141,7 @@ ${guard}
|
|
|
11938
12141
|
})();`.trim();
|
|
11939
12142
|
case 'add_transition':
|
|
11940
12143
|
return `
|
|
11941
|
-
// ${input.transition?.type ?? 'crossfade'} between ${input.fromItemId} and ${input.toItemId}
|
|
12144
|
+
// ${oneLine(input.transition?.type ?? 'crossfade')} between ${oneLine(input.fromItemId)} and ${oneLine(input.toItemId)}
|
|
11942
12145
|
(function() {
|
|
11943
12146
|
${guard}
|
|
11944
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.' }; }
|
|
@@ -12060,7 +12263,7 @@ ${guard}
|
|
|
12060
12263
|
...(input.live !== undefined ? { live: input.live } : {}),
|
|
12061
12264
|
});
|
|
12062
12265
|
return `
|
|
12063
|
-
// 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)' : '')}
|
|
12064
12267
|
(async function() {
|
|
12065
12268
|
// A media id (vraster_ / araster_, what upload returns) is resolved to the
|
|
12066
12269
|
// registry id these app facades take (round 10, 10.12).
|
|
@@ -12145,7 +12348,7 @@ ${guard}
|
|
|
12145
12348
|
...(input.fontSize !== undefined ? { fontSize: input.fontSize } : {}),
|
|
12146
12349
|
});
|
|
12147
12350
|
return `
|
|
12148
|
-
// 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
|
|
12149
12352
|
(async function() {
|
|
12150
12353
|
if (typeof app.applyTextStyle !== 'function') { return { success: false, error: 'app.applyTextStyle unavailable — update FxTool to a text-styles build' }; }
|
|
12151
12354
|
// LOAD THE FACES FIRST. The decorative stylesheet is fetched on pointerenter
|
|
@@ -12296,7 +12499,7 @@ ${guard}
|
|
|
12296
12499
|
const S = (v) => JSON.stringify(v);
|
|
12297
12500
|
const id = S(input.itemId);
|
|
12298
12501
|
return `
|
|
12299
|
-
// Choreograph ${input.beats.length} beat(s) for ${input.itemId}
|
|
12502
|
+
// Choreograph ${oneLine(input.beats.length)} beat(s) for ${oneLine(input.itemId)}
|
|
12300
12503
|
(function() {
|
|
12301
12504
|
const __it = (app.getItemById && app.getItemById(${id}))
|
|
12302
12505
|
|| (app.itemRegistry && app.itemRegistry.get && (app.itemRegistry.get(${id}) || {}).item);
|
|
@@ -12404,7 +12607,7 @@ ${input.sound ? ` // SOUND ON THE ACTION (D68): one placed sound per moment the
|
|
|
12404
12607
|
const cam = input.camera === true ? { fill: 0.45 } : (input.camera || null);
|
|
12405
12608
|
const sound = input.sound === true ? 'whoosh' : (input.sound || null);
|
|
12406
12609
|
return `
|
|
12407
|
-
// Morph sequence: ${states.length} state(s)
|
|
12610
|
+
// Morph sequence: ${oneLine(states.length)} state(s)
|
|
12408
12611
|
(function() {
|
|
12409
12612
|
const src = app.getItemById && app.getItemById(${S(input.itemId)});
|
|
12410
12613
|
if (!src) { return { success: false, error: ${S(input.itemId)} + ' is not on the canvas.' }; }
|
|
@@ -12881,7 +13084,7 @@ ${cam ? ` // The camera rides the SAME timing: framing each state as its morph
|
|
|
12881
13084
|
}
|
|
12882
13085
|
})();
|
|
12883
13086
|
return `
|
|
12884
|
-
// Shader graph: ${input.action}
|
|
13087
|
+
// Shader graph: ${oneLine(input.action)}
|
|
12885
13088
|
(async function() {
|
|
12886
13089
|
${head}
|
|
12887
13090
|
${body}
|
|
@@ -12902,7 +13105,7 @@ ${body}
|
|
|
12902
13105
|
// or brush-only medium with the reason. The Paper item it returns is not
|
|
12903
13106
|
// passed back (it cannot cross the bridge); its id is.
|
|
12904
13107
|
return `
|
|
12905
|
-
// Medium: apply ${input.medium}
|
|
13108
|
+
// Medium: apply ${oneLine(input.medium)}
|
|
12906
13109
|
(async function() {
|
|
12907
13110
|
if (typeof app.applyMedium !== 'function') { return { success: false, error: 'app.applyMedium unavailable — update FxTool' }; }
|
|
12908
13111
|
const r = await app.applyMedium(${S(input.itemId)}, ${S(input.medium)}, ${S(input.options ?? {})});
|
|
@@ -13096,7 +13299,7 @@ ${body}
|
|
|
13096
13299
|
...(input.keepSource !== undefined ? { keepSource: input.keepSource } : {}),
|
|
13097
13300
|
});
|
|
13098
13301
|
return `
|
|
13099
|
-
// 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
|
|
13100
13303
|
(async function() {
|
|
13101
13304
|
if (typeof app.applyTextEffect !== 'function') { return { success: false, error: 'app.applyTextEffect unavailable — update FxTool to a text-effects build' }; }
|
|
13102
13305
|
const r = await app.applyTextEffect(${S(input.itemId)}, ${S(input.effect)}, ${opts});
|
|
@@ -13168,7 +13371,7 @@ ${body}
|
|
|
13168
13371
|
...(input.keepSource !== undefined ? { keepSource: input.keepSource } : {}),
|
|
13169
13372
|
});
|
|
13170
13373
|
return `
|
|
13171
|
-
// 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)
|
|
13172
13375
|
(function() {
|
|
13173
13376
|
if (typeof app.shatterImage !== 'function') { return { success: false, error: 'app.shatterImage unavailable — update FxTool to a shatter-capable build' }; }
|
|
13174
13377
|
const r = app.shatterImage(${S(input.itemId)}, ${opts});
|
|
@@ -13303,7 +13506,10 @@ ${body}
|
|
|
13303
13506
|
const needWorld = ` if (!app._world3d) { return { success: false, error: "no 3D world — call pinepaper_world3d create first" }; }`;
|
|
13304
13507
|
switch (input.action) {
|
|
13305
13508
|
case 'create': {
|
|
13306
|
-
|
|
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 } : {}) });
|
|
13307
13513
|
return `
|
|
13308
13514
|
// World3D: create — terrain, sky, shadows, walkable character, under the Paper canvas
|
|
13309
13515
|
(async function() {
|
|
@@ -13316,7 +13522,11 @@ ${body}
|
|
|
13316
13522
|
// params is WORLD_SCHEMA: an ARRAY of {path, kind, doc}. Object.keys on it
|
|
13317
13523
|
// returned "0", "1", "2"… — the indexes, not a single usable key.
|
|
13318
13524
|
const keys = !d ? [] : Array.isArray(d.params) ? d.params.map(function (e) { return e && e.path; }).filter(Boolean) : Object.keys(d.params || d);
|
|
13319
|
-
|
|
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;
|
|
13320
13530
|
})();`.trim();
|
|
13321
13531
|
}
|
|
13322
13532
|
case 'describe':
|
|
@@ -13346,7 +13556,7 @@ ${needWorld}
|
|
|
13346
13556
|
...(input.live !== undefined ? { live: input.live } : {}),
|
|
13347
13557
|
});
|
|
13348
13558
|
return `
|
|
13349
|
-
// 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)' : '')}
|
|
13350
13560
|
(function() {
|
|
13351
13561
|
if (typeof app.addWorldActor !== 'function') { return { success: false, error: 'app.addWorldActor unavailable — update FxTool' }; }
|
|
13352
13562
|
${needWorld}
|
|
@@ -13449,7 +13659,7 @@ ${needWorld}
|
|
|
13449
13659
|
mesh.uniforms = uniforms;
|
|
13450
13660
|
}
|
|
13451
13661
|
return `
|
|
13452
|
-
// World3D: ${input.action} — a canvas path becomes real geometry
|
|
13662
|
+
// World3D: ${oneLine(input.action)} — a canvas path becomes real geometry
|
|
13453
13663
|
(async function() {
|
|
13454
13664
|
// @engine-methods extrudeToMesh latheToMesh importOBJToWorld importGLTFToWorld
|
|
13455
13665
|
if (typeof app.${fn} !== 'function') { return { success: false, error: 'app.${fn} unavailable — update FxTool to a world3d-capable build' }; }
|
|
@@ -13556,7 +13766,7 @@ ${needWorld}
|
|
|
13556
13766
|
case 'import_gltf': {
|
|
13557
13767
|
const fn = input.action === 'import_obj' ? 'importOBJToWorld' : 'importGLTFToWorld';
|
|
13558
13768
|
return `
|
|
13559
|
-
// World3D: ${input.action}
|
|
13769
|
+
// World3D: ${oneLine(input.action)}
|
|
13560
13770
|
(async function() {
|
|
13561
13771
|
// @engine-methods extrudeToMesh latheToMesh importOBJToWorld importGLTFToWorld
|
|
13562
13772
|
if (typeof app.${fn} !== 'function') { return { success: false, error: 'app.${fn} unavailable — update FxTool to a world3d-capable build' }; }
|
|
@@ -13758,7 +13968,7 @@ ${needWorld}
|
|
|
13758
13968
|
generateDesignCompose(scene, ops, style, sizeWarning, report = {}) {
|
|
13759
13969
|
const S = (v) => JSON.stringify(v);
|
|
13760
13970
|
return `
|
|
13761
|
-
// 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
|
|
13762
13972
|
(function() {
|
|
13763
13973
|
if (typeof app.create !== 'function') { return { success: false, error: 'app.create unavailable — update FxTool' }; }
|
|
13764
13974
|
const size = ${S({ width: scene.width, height: scene.height })};
|
|
@@ -13838,7 +14048,7 @@ ${needWorld}
|
|
|
13838
14048
|
}
|
|
13839
14049
|
const fn = action === 'figure' ? 'stickFigure' : 'stickSet';
|
|
13840
14050
|
return `
|
|
13841
|
-
// Stick: ${action}
|
|
14051
|
+
// Stick: ${oneLine(action)}
|
|
13842
14052
|
(async function() {
|
|
13843
14053
|
// @engine-methods stickFigure stickSet
|
|
13844
14054
|
if (typeof app.${fn} !== 'function') { return { success: false, error: 'app.${fn} unavailable — update FxTool (the stick kit is vendored from mcp-cloud)' }; }
|
|
@@ -13932,7 +14142,7 @@ ${needWorld}
|
|
|
13932
14142
|
case 'export_dotlottie': {
|
|
13933
14143
|
const fn = input.action === 'export_lottie' ? 'exportLottie' : 'exportDotLottie';
|
|
13934
14144
|
return `
|
|
13935
|
-
// Interchange: ${fn}
|
|
14145
|
+
// Interchange: ${oneLine(fn)}
|
|
13936
14146
|
(async function() {
|
|
13937
14147
|
${guard(fn)}
|
|
13938
14148
|
${TO_DATA_URL_JS}
|
|
@@ -14049,7 +14259,7 @@ ${guard('exportPNGSequence')}
|
|
|
14049
14259
|
const S = (v) => JSON.stringify(v);
|
|
14050
14260
|
const guard = (fn) => ` if (typeof app.${fn} !== 'function') { return { success: false, error: 'app.${fn} unavailable — update FxTool' }; }`;
|
|
14051
14261
|
const simple = (fn, args, key, label) => key === 'played' ? `
|
|
14052
|
-
// Sound: ${label}
|
|
14262
|
+
// Sound: ${oneLine(label)}
|
|
14053
14263
|
(function() {
|
|
14054
14264
|
${guard(fn)}
|
|
14055
14265
|
// A play that returns no voice played nothing, and reported success (gate
|
|
@@ -14058,7 +14268,7 @@ ${guard(fn)}
|
|
|
14058
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.' }; }
|
|
14059
14269
|
return { success: true, played: __v };
|
|
14060
14270
|
})();`.trim() : `
|
|
14061
|
-
// Sound: ${label}
|
|
14271
|
+
// Sound: ${oneLine(label)}
|
|
14062
14272
|
(function() {
|
|
14063
14273
|
${guard(fn)}
|
|
14064
14274
|
return { success: true, ${key}: app.${fn}(${args}) };
|
|
@@ -14080,7 +14290,7 @@ ${guard(fn)}
|
|
|
14080
14290
|
: simple('playSoundFromText', S(input.text), 'played', 'a described sound');
|
|
14081
14291
|
case 'from_text':
|
|
14082
14292
|
return `
|
|
14083
|
-
// Sound: resolve
|
|
14293
|
+
// Sound: resolve ${jsLiteral(input.text)} to a spec WITHOUT playing it
|
|
14084
14294
|
(function() {
|
|
14085
14295
|
${guard('buildSoundFromText')}
|
|
14086
14296
|
const spec = app.buildSoundFromText(${S(input.text)});
|
|
@@ -14123,7 +14333,7 @@ ${guard('buildSoundFromText')}
|
|
|
14123
14333
|
// checking the method a caller needs. It extracted only
|
|
14124
14334
|
// renderSoundtrackWav until this line existed.
|
|
14125
14335
|
return `
|
|
14126
|
-
// Sound: define ${input.name} at runtime
|
|
14336
|
+
// Sound: define ${oneLine(input.name)} at runtime
|
|
14127
14337
|
(function() {
|
|
14128
14338
|
${guard(fn)}
|
|
14129
14339
|
var name;
|
|
@@ -14307,7 +14517,7 @@ ${guard('timbreFromPath')}
|
|
|
14307
14517
|
opts[k] = input[k];
|
|
14308
14518
|
}
|
|
14309
14519
|
return `
|
|
14310
|
-
// 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}
|
|
14311
14521
|
(async function() {
|
|
14312
14522
|
if (typeof app.animateItems !== 'function') { return { success: false, error: 'app.animateItems unavailable — update FxTool' }; }
|
|
14313
14523
|
const r = await app.animateItems(${target}, ${S(opts)});
|
|
@@ -14345,7 +14555,7 @@ ${guard('timbreFromPath')}
|
|
|
14345
14555
|
switch (input.action) {
|
|
14346
14556
|
case 'boolean':
|
|
14347
14557
|
return `
|
|
14348
|
-
// Path boolean: ${input.op} — CONSUMES its operands and leaves one result
|
|
14558
|
+
// Path boolean: ${oneLine(input.op)} — CONSUMES its operands and leaves one result
|
|
14349
14559
|
(function() {
|
|
14350
14560
|
${guard('booleanOp')}${pass(`app.booleanOp(${S(input.itemIds)}, ${S(input.op)})`)}
|
|
14351
14561
|
})();`.trim();
|
|
@@ -14369,7 +14579,7 @@ ${guard('togglePathClosed')}${pass(`app.togglePathClosed(${S(input.itemId)})`)}
|
|
|
14369
14579
|
})();`.trim();
|
|
14370
14580
|
case 'pattern':
|
|
14371
14581
|
return `
|
|
14372
|
-
// Pattern: repeat the item — ${input.kind}
|
|
14582
|
+
// Pattern: repeat the item — ${oneLine(input.kind)}
|
|
14373
14583
|
(function() {
|
|
14374
14584
|
${guard('patternFrom')}${pass(`app.patternFrom(${S(input.itemId)}, ${S(input.kind)}, ${S(input.pattern ?? {})})`)}
|
|
14375
14585
|
})();`.trim();
|
|
@@ -14405,7 +14615,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14405
14615
|
generateCropImage(input) {
|
|
14406
14616
|
const opts = input.aspectRatio ? JSON.stringify({ aspectRatio: input.aspectRatio }) : '{}';
|
|
14407
14617
|
return `
|
|
14408
|
-
// Crop image ${input.itemId}
|
|
14618
|
+
// Crop image ${oneLine(input.itemId)}
|
|
14409
14619
|
(async function() {
|
|
14410
14620
|
if (typeof app.cropImage !== 'function') { return { success: false, error: 'app.cropImage unavailable — update FxTool to a build with one-shot image ops' }; }
|
|
14411
14621
|
const result = await app.cropImage(${JSON.stringify(input.itemId)}, ${JSON.stringify(input.rect)}, ${opts});
|
|
@@ -14424,7 +14634,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14424
14634
|
...(input.smoothing !== undefined ? { smoothing: input.smoothing } : {}),
|
|
14425
14635
|
});
|
|
14426
14636
|
return `
|
|
14427
|
-
// Chroma-key image ${input.itemId}
|
|
14637
|
+
// Chroma-key image ${oneLine(input.itemId)}
|
|
14428
14638
|
(async function() {
|
|
14429
14639
|
if (typeof app.applyChromaKey !== 'function') { return { success: false, error: 'app.applyChromaKey unavailable — update FxTool to a build with one-shot image ops' }; }
|
|
14430
14640
|
const result = await app.applyChromaKey(${JSON.stringify(input.itemId)}, ${opts});
|
|
@@ -14840,7 +15050,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14840
15050
|
const argsJson = JSON.stringify(input.args ?? []);
|
|
14841
15051
|
const createAsJson = input.createAs ? JSON.stringify(input.createAs) : 'null';
|
|
14842
15052
|
return `
|
|
14843
|
-
// Geometry construction: ${input.operation}
|
|
15053
|
+
// Geometry construction: ${oneLine(input.operation)}
|
|
14844
15054
|
(function() {
|
|
14845
15055
|
const g = app.geometry;
|
|
14846
15056
|
if (!g || typeof g[${op}] !== 'function') {
|
|
@@ -14902,7 +15112,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14902
15112
|
const optsJson = JSON.stringify(input);
|
|
14903
15113
|
const kindJson = JSON.stringify(input.kind);
|
|
14904
15114
|
return `
|
|
14905
|
-
// Equation-driven path: ${input.kind}${input.preset ? ' ' + input.preset : ''}
|
|
15115
|
+
// Equation-driven path: ${oneLine(input.kind)}${oneLine(input.preset ? ' ' + input.preset : '')}
|
|
14906
15116
|
(function() {
|
|
14907
15117
|
if (typeof app.createEquationPath !== 'function') {
|
|
14908
15118
|
return { success: false, error: 'app.createEquationPath unavailable — update FxTool to a build with the Expression IR equation-path API (S10 B5)' };
|
|
@@ -14926,7 +15136,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14926
15136
|
const nameJson = JSON.stringify(input.name);
|
|
14927
15137
|
const optsJson = JSON.stringify({ payloadType: input.payloadType, x: input.x, y: input.y, ...(input.at !== undefined ? { at: input.at } : {}) });
|
|
14928
15138
|
return `
|
|
14929
|
-
// Create event: ${input.name}
|
|
15139
|
+
// Create event: ${oneLine(input.name)}
|
|
14930
15140
|
(function() {
|
|
14931
15141
|
if (typeof app.createEvent !== 'function') {
|
|
14932
15142
|
return { success: false, error: 'app.createEvent unavailable — update FxTool to a build with the event system (S11)' };
|
|
@@ -14941,7 +15151,7 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
14941
15151
|
const eventIdJson = JSON.stringify(input.eventId);
|
|
14942
15152
|
const payloadJson = JSON.stringify(input.payload ?? null);
|
|
14943
15153
|
return `
|
|
14944
|
-
// Pulse event: ${input.eventId}
|
|
15154
|
+
// Pulse event: ${oneLine(input.eventId)}
|
|
14945
15155
|
(function() {
|
|
14946
15156
|
if (typeof app.pulseEvent !== 'function') {
|
|
14947
15157
|
return { success: false, error: 'app.pulseEvent unavailable — update FxTool to a build with the event system (S11)' };
|
|
@@ -15086,7 +15296,7 @@ return {
|
|
|
15086
15296
|
*/
|
|
15087
15297
|
export function generateAutoKeyCode(itemId, props, atTime, easing) {
|
|
15088
15298
|
return `
|
|
15089
|
-
// Auto-key ${itemId} at ${atTime}s
|
|
15299
|
+
// Auto-key ${oneLine(itemId)} at ${oneLine(atTime)}s
|
|
15090
15300
|
(function() {
|
|
15091
15301
|
const item = app.getItemById(${JSON.stringify(itemId)});
|
|
15092
15302
|
if (!item) return { success: false, error: ${JSON.stringify(`no item "${itemId}" on the canvas — check the id with pinepaper_get_items.`)} };
|
|
@@ -15144,7 +15354,7 @@ export function generateAutoKeyCode(itemId, props, atTime, easing) {
|
|
|
15144
15354
|
*/
|
|
15145
15355
|
export function generateQueryMutationsCode(itemId, property, range, fps = 30) {
|
|
15146
15356
|
return `
|
|
15147
|
-
// Keyframe series for ${itemId}${property ? '.' + property : ''}
|
|
15357
|
+
// Keyframe series for ${oneLine(itemId)}${oneLine(property ? '.' + property : '')}
|
|
15148
15358
|
(function() {
|
|
15149
15359
|
const item = app.getItemById(${JSON.stringify(itemId)});
|
|
15150
15360
|
if (!item) return { success: false, error: ${JSON.stringify(`no item "${itemId}" on the canvas.`)} };
|