@pinepaper.studio/mcp-server 1.5.3 → 1.5.5
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 +6 -4
- package/dist/agent/types.d.ts +16 -16
- package/dist/browser/url-target.d.ts +26 -0
- package/dist/browser/url-target.d.ts.map +1 -0
- package/dist/browser/url-target.js +50 -0
- package/dist/browser/url-target.js.map +1 -0
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -4
- package/dist/index.js.map +1 -1
- package/dist/ontology/hints.d.ts +71 -0
- package/dist/ontology/hints.d.ts.map +1 -0
- package/dist/ontology/hints.js +189 -0
- package/dist/ontology/hints.js.map +1 -0
- package/dist/ontology/vocabulary.d.ts.map +1 -1
- package/dist/ontology/vocabulary.js +34 -0
- package/dist/ontology/vocabulary.js.map +1 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +146 -0
- package/dist/prompts/index.js.map +1 -1
- package/dist/tools/compact-descriptions.d.ts +3 -0
- package/dist/tools/compact-descriptions.d.ts.map +1 -1
- package/dist/tools/compact-descriptions.js +17 -9
- package/dist/tools/compact-descriptions.js.map +1 -1
- package/dist/tools/definitions.d.ts +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +1251 -2327
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/handlers/camera.d.ts +19 -0
- package/dist/tools/handlers/camera.d.ts.map +1 -0
- package/dist/tools/handlers/camera.js +100 -0
- package/dist/tools/handlers/camera.js.map +1 -0
- package/dist/tools/handlers/export.d.ts +12 -0
- package/dist/tools/handlers/export.d.ts.map +1 -0
- package/dist/tools/handlers/export.js +30 -0
- package/dist/tools/handlers/export.js.map +1 -0
- package/dist/tools/handlers/font.d.ts +12 -0
- package/dist/tools/handlers/font.d.ts.map +1 -0
- package/dist/tools/handlers/font.js +135 -0
- package/dist/tools/handlers/font.js.map +1 -0
- package/dist/tools/handlers/maps.d.ts +19 -0
- package/dist/tools/handlers/maps.d.ts.map +1 -0
- package/dist/tools/handlers/maps.js +184 -0
- package/dist/tools/handlers/maps.js.map +1 -0
- package/dist/tools/handlers/ontology.d.ts +20 -0
- package/dist/tools/handlers/ontology.d.ts.map +1 -0
- package/dist/tools/handlers/ontology.js +185 -0
- package/dist/tools/handlers/ontology.js.map +1 -0
- package/dist/tools/handlers/tool-guide.d.ts +14 -0
- package/dist/tools/handlers/tool-guide.d.ts.map +1 -0
- package/dist/tools/handlers/tool-guide.js +52 -0
- package/dist/tools/handlers/tool-guide.js.map +1 -0
- package/dist/tools/handlers.d.ts +18 -0
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +213 -695
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts +4 -0
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +45 -66
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +36 -62
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +35 -34
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +869 -474
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +1047 -988
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +282 -84
- package/dist/types/schemas.js.map +1 -1
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +18 -0
- package/dist/version.js.map +1 -0
- package/package.json +1 -1
|
@@ -10,10 +10,6 @@ import { CreateItemInputSchema, ModifyItemInputSchema, AddRelationInputSchema, A
|
|
|
10
10
|
CreateDiagramShapeInputSchema, ConnectInputSchema, ConnectPortsInputSchema, AddPortsInputSchema, AutoLayoutInputSchema, GetDiagramShapesInputSchema, UpdateConnectorInputSchema, RemoveConnectorInputSchema, DiagramModeInputSchema,
|
|
11
11
|
// Agent flow mode types
|
|
12
12
|
AgentStartJobInputSchema, AgentEndJobInputSchema, AgentResetInputSchema, AgentBatchExecuteInputSchema, AgentExportInputSchema, AgentAnalyzeInputSchema,
|
|
13
|
-
// Interactive/Trigger types
|
|
14
|
-
AddTriggerInputSchema, RemoveTriggerInputSchema, QueryTriggersInputSchema,
|
|
15
|
-
// Quiz types
|
|
16
|
-
CreateQuizInputSchema, GetQuizStateInputSchema, ResetQuizInputSchema,
|
|
17
13
|
// Letter collage types
|
|
18
14
|
CreateLetterCollageInputSchema, AnimateLetterCollageInputSchema,
|
|
19
15
|
// Map types
|
|
@@ -82,7 +78,7 @@ function generateBlendModeCode(itemVarName, blendMode) {
|
|
|
82
78
|
*/
|
|
83
79
|
function generateCreateItemCode(itemType, position, properties) {
|
|
84
80
|
// Extract special properties that need separate handling
|
|
85
|
-
const { color, fillColor, strokeColor, shadowColor, shadowBlur, shadowOffset, blendMode, opacity, ...baseProperties } = properties;
|
|
81
|
+
const { color, fillColor, strokeColor, shadowColor, shadowBlur, shadowOffset, blendMode, opacity, contentType, contentFormat, countdownTarget, countdownEndText, ...baseProperties } = properties;
|
|
86
82
|
const params = {
|
|
87
83
|
x: position.x,
|
|
88
84
|
y: position.y,
|
|
@@ -127,6 +123,17 @@ const item = app.create('${itemType}', ${JSON.stringify(params, null, 2)});`;
|
|
|
127
123
|
}
|
|
128
124
|
// Ensure item is visible above backgrounds/generators
|
|
129
125
|
code += `\nif (item.bringToFront) item.bringToFront();`;
|
|
126
|
+
// Add dynamic content support for text items
|
|
127
|
+
if (contentType && itemType === 'text') {
|
|
128
|
+
const opts = {};
|
|
129
|
+
if (contentFormat)
|
|
130
|
+
opts.format = contentFormat;
|
|
131
|
+
if (countdownTarget !== undefined)
|
|
132
|
+
opts.countdownTarget = countdownTarget;
|
|
133
|
+
if (countdownEndText)
|
|
134
|
+
opts.countdownEndText = countdownEndText;
|
|
135
|
+
code += `\nif (app.setDynamicContent) app.setDynamicContent(item, '${contentType}', ${JSON.stringify(opts)});`;
|
|
136
|
+
}
|
|
130
137
|
code += `
|
|
131
138
|
const itemId = item.data.registryId;
|
|
132
139
|
app.historyManager.saveState();
|
|
@@ -139,15 +146,42 @@ app.historyManager.saveState();
|
|
|
139
146
|
* Template for modifying items
|
|
140
147
|
*/
|
|
141
148
|
function generateModifyItemCode(itemId, properties) {
|
|
142
|
-
|
|
149
|
+
const { contentType, contentFormat, countdownTarget, countdownEndText, ...restProperties } = properties;
|
|
150
|
+
let code = `
|
|
143
151
|
// Modify item ${itemId}
|
|
144
|
-
app.select('${itemId}')
|
|
145
|
-
app.modify
|
|
152
|
+
app.select('${itemId}');`;
|
|
153
|
+
// Pass non-dynamic-content properties to app.modify
|
|
154
|
+
if (Object.keys(restProperties).length > 0) {
|
|
155
|
+
code += `\napp.modify(${JSON.stringify(restProperties, null, 2)});`;
|
|
156
|
+
}
|
|
157
|
+
// Handle dynamic content type changes
|
|
158
|
+
if (contentType !== undefined) {
|
|
159
|
+
if (contentType === null || contentType === 'none' || contentType === '') {
|
|
160
|
+
// Remove dynamic content
|
|
161
|
+
code += `
|
|
162
|
+
const entry = app.itemRegistry.get('${itemId}');
|
|
163
|
+
if (entry && entry.item && app.removeDynamicContent) app.removeDynamicContent(entry.item);`;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// Set dynamic content
|
|
167
|
+
const opts = {};
|
|
168
|
+
if (contentFormat)
|
|
169
|
+
opts.format = contentFormat;
|
|
170
|
+
if (countdownTarget !== undefined)
|
|
171
|
+
opts.countdownTarget = countdownTarget;
|
|
172
|
+
if (countdownEndText)
|
|
173
|
+
opts.countdownEndText = countdownEndText;
|
|
174
|
+
code += `
|
|
175
|
+
const entry = app.itemRegistry.get('${itemId}');
|
|
176
|
+
if (entry && entry.item && app.setDynamicContent) app.setDynamicContent(entry.item, '${contentType}', ${JSON.stringify(opts)});`;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
code += `
|
|
146
180
|
app.historyManager.saveState();
|
|
147
181
|
|
|
148
182
|
// Return success
|
|
149
|
-
({ success: true, itemId: '${itemId}' })
|
|
150
|
-
|
|
183
|
+
({ success: true, itemId: '${itemId}' });`;
|
|
184
|
+
return code.trim();
|
|
151
185
|
}
|
|
152
186
|
/**
|
|
153
187
|
* Template for deleting items
|
|
@@ -254,17 +288,40 @@ app.animate(item, {
|
|
|
254
288
|
({ success: true, itemId: '${itemId}', animationType: '${animationType}' });
|
|
255
289
|
`.trim();
|
|
256
290
|
}
|
|
291
|
+
/**
|
|
292
|
+
* PinePaper's keyframe interpolator only tweens `position` when it is an [x, y]
|
|
293
|
+
* ARRAY. Callers (and other agents) routinely pass a { x, y } object, which is
|
|
294
|
+
* silently ignored — the item then stays frozen for the whole animation. Accept
|
|
295
|
+
* both shapes by normalizing { x, y } -> [x, y] before the keyframes are emitted.
|
|
296
|
+
*/
|
|
297
|
+
function normalizeKeyframePositions(keyframes) {
|
|
298
|
+
return (keyframes || []).map((kf) => {
|
|
299
|
+
const props = kf?.properties;
|
|
300
|
+
const p = props?.position;
|
|
301
|
+
if (p && !Array.isArray(p) && typeof p === 'object' && 'x' in p && 'y' in p) {
|
|
302
|
+
return { ...kf, properties: { ...props, position: [p.x, p.y] } };
|
|
303
|
+
}
|
|
304
|
+
return kf;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
257
307
|
/**
|
|
258
308
|
* Template for keyframe animation
|
|
259
309
|
*/
|
|
260
|
-
function generateKeyframeAnimateCode(itemId, keyframes, duration, loop = false) {
|
|
261
|
-
const keyframesJson = JSON.stringify(keyframes, null, 2);
|
|
310
|
+
function generateKeyframeAnimateCode(itemId, keyframes, duration, loop = false, timeOffset, clipInPoint, clipOutPoint) {
|
|
311
|
+
const keyframesJson = JSON.stringify(normalizeKeyframePositions(keyframes), null, 2);
|
|
262
312
|
const calculatedDuration = duration || Math.max(...keyframes.map(k => k.time));
|
|
313
|
+
const opts = { duration: calculatedDuration, loop };
|
|
314
|
+
if (timeOffset !== undefined)
|
|
315
|
+
opts.timeOffset = timeOffset;
|
|
316
|
+
if (clipInPoint !== undefined)
|
|
317
|
+
opts.clipInPoint = clipInPoint;
|
|
318
|
+
if (clipOutPoint !== undefined)
|
|
319
|
+
opts.clipOutPoint = clipOutPoint;
|
|
263
320
|
return `
|
|
264
321
|
// Apply keyframe animation to ${itemId}
|
|
265
|
-
app.addAnimation('${itemId}', ${keyframesJson},
|
|
322
|
+
app.addAnimation('${itemId}', ${keyframesJson}, ${JSON.stringify(opts)});
|
|
266
323
|
|
|
267
|
-
({ success: true, itemId: '${itemId}', duration: ${calculatedDuration}, loop: ${loop} });
|
|
324
|
+
({ success: true, itemId: '${itemId}', duration: ${calculatedDuration}, loop: ${loop}${timeOffset !== undefined ? `, timeOffset: ${timeOffset}` : ''}${clipInPoint !== undefined ? `, clipInPoint: ${clipInPoint}` : ''}${clipOutPoint !== undefined ? `, clipOutPoint: ${clipOutPoint}` : ''} });
|
|
268
325
|
`.trim();
|
|
269
326
|
}
|
|
270
327
|
/**
|
|
@@ -411,35 +468,29 @@ const size = app.getCanvasSize();
|
|
|
411
468
|
*/
|
|
412
469
|
function generateClearCanvasCode() {
|
|
413
470
|
return `
|
|
414
|
-
// Clear all items from canvas
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Also clear any relations
|
|
433
|
-
if (app.clearAllRelations) {
|
|
434
|
-
app.clearAllRelations();
|
|
471
|
+
// Clear all items from canvas. Prefer app.clearCanvas() — the canonical
|
|
472
|
+
// reset that also tears down relations, the (virtual) camera animation,
|
|
473
|
+
// generators, effects and background. The old manual loop here was broken:
|
|
474
|
+
// it keyed item lookups on entry.id/entry.itemId (the registry getAll()
|
|
475
|
+
// entries don't expose those — getItemById wants the registry id), so it
|
|
476
|
+
// removed 0 items, and it called the non-existent app.clearAllRelations.
|
|
477
|
+
const before = app.itemRegistry ? app.itemRegistry.getAll().length : 0;
|
|
478
|
+
|
|
479
|
+
if (typeof app.clearCanvas === 'function') {
|
|
480
|
+
app.clearCanvas();
|
|
481
|
+
} else {
|
|
482
|
+
// Fallback for older builds without clearCanvas().
|
|
483
|
+
if (app.textItemGroup) app.textItemGroup.removeChildren();
|
|
484
|
+
if (app.patternGroup) app.patternGroup.removeChildren();
|
|
485
|
+
if (app.itemRegistry) app.itemRegistry.clear();
|
|
486
|
+
if (app.relationRegistry) app.relationRegistry.clear();
|
|
435
487
|
}
|
|
436
488
|
|
|
437
|
-
app.historyManager.saveState();
|
|
489
|
+
if (app.historyManager) app.historyManager.saveState();
|
|
438
490
|
|
|
439
|
-
|
|
440
|
-
const remaining = app.itemRegistry.getAll().length;
|
|
491
|
+
const remaining = app.itemRegistry ? app.itemRegistry.getAll().length : 0;
|
|
441
492
|
|
|
442
|
-
({ success: true, removedCount, remainingItems: remaining });
|
|
493
|
+
({ success: true, removedCount: Math.max(0, before - remaining), remainingItems: remaining });
|
|
443
494
|
`.trim();
|
|
444
495
|
}
|
|
445
496
|
/**
|
|
@@ -582,7 +633,14 @@ export class PinePaperCodeGenerator {
|
|
|
582
633
|
*/
|
|
583
634
|
generateCreateItem(input) {
|
|
584
635
|
const validated = CreateItemInputSchema.parse(input);
|
|
585
|
-
|
|
636
|
+
const properties = { ...validated.properties };
|
|
637
|
+
if (validated.animationType !== undefined)
|
|
638
|
+
properties.animationType = validated.animationType;
|
|
639
|
+
if (validated.animationSpeed !== undefined)
|
|
640
|
+
properties.animationSpeed = validated.animationSpeed;
|
|
641
|
+
if (validated.keyframes !== undefined)
|
|
642
|
+
properties.keyframes = validated.keyframes;
|
|
643
|
+
return generateCreateItemCode(validated.itemType, validated.position, properties);
|
|
586
644
|
}
|
|
587
645
|
/**
|
|
588
646
|
* Generate code for modifying an item
|
|
@@ -628,7 +686,7 @@ export class PinePaperCodeGenerator {
|
|
|
628
686
|
*/
|
|
629
687
|
generateKeyframeAnimate(input) {
|
|
630
688
|
const validated = KeyframeAnimateInputSchema.parse(input);
|
|
631
|
-
return generateKeyframeAnimateCode(validated.itemId, validated.keyframes, validated.duration, validated.loop);
|
|
689
|
+
return generateKeyframeAnimateCode(validated.itemId, validated.keyframes, validated.duration, validated.loop, validated.timeOffset, validated.clipInPoint, validated.clipOutPoint);
|
|
632
690
|
}
|
|
633
691
|
/**
|
|
634
692
|
* Generate code for executing a generator
|
|
@@ -748,6 +806,70 @@ if (imported) {
|
|
|
748
806
|
return `
|
|
749
807
|
// Error: No SVG source provided
|
|
750
808
|
throw new Error('Either svgString or url must be provided');
|
|
809
|
+
`.trim();
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Generate code for importing a Mermaid diagram (flowchart, stateDiagram,
|
|
813
|
+
* sequenceDiagram, erDiagram, classDiagram).
|
|
814
|
+
*/
|
|
815
|
+
generateImportMermaid(mermaidText, options = {}) {
|
|
816
|
+
const escaped = mermaidText.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
817
|
+
const optsJson = JSON.stringify(options);
|
|
818
|
+
return `
|
|
819
|
+
// Import Mermaid diagram
|
|
820
|
+
(function() {
|
|
821
|
+
if (!app.importMermaid) return { success: false, error: 'importMermaid not available — diagramSystem missing' };
|
|
822
|
+
const mermaidText = \`${escaped}\`;
|
|
823
|
+
const result = app.importMermaid(mermaidText, ${optsJson});
|
|
824
|
+
return {
|
|
825
|
+
success: !!result.success,
|
|
826
|
+
nodeCount: result.nodes ? result.nodes.length : 0,
|
|
827
|
+
edgeCount: result.edges ? result.edges.length : 0,
|
|
828
|
+
nodes: result.nodes || [],
|
|
829
|
+
edges: result.edges || [],
|
|
830
|
+
errors: result.errors || [],
|
|
831
|
+
};
|
|
832
|
+
})();
|
|
833
|
+
`.trim();
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Generate code for capturing the canvas as pp: ontology. Wraps FxTool's
|
|
837
|
+
* app.exportCanvasOntology() — defined in PinePaper.js after the
|
|
838
|
+
* canvas-state-to-ontology promotion. Output mirrors the cloud-sync
|
|
839
|
+
* postMessage contract: { canvasWidth, canvasHeight, itemCount,
|
|
840
|
+
* itemTypes, hasAnimations, relationCount, items, triples, [viewport] }.
|
|
841
|
+
*
|
|
842
|
+
* Failure modes: app missing the method → return { success: false, error }
|
|
843
|
+
* with a clear FxTool-version hint. Internal exceptions propagate from
|
|
844
|
+
* the browser-side function (TripleLanguage warns to console; we don't
|
|
845
|
+
* fail the tool call on triple-only failures).
|
|
846
|
+
*/
|
|
847
|
+
generateGetCanvasOntology(options = {}) {
|
|
848
|
+
const optsJson = JSON.stringify(options);
|
|
849
|
+
return `
|
|
850
|
+
// Capture canvas as pp: ontology
|
|
851
|
+
(function() {
|
|
852
|
+
if (!app.exportCanvasOntology) {
|
|
853
|
+
return {
|
|
854
|
+
success: false,
|
|
855
|
+
error: 'app.exportCanvasOntology not available — requires FxTool with canvas-ontology promotion. Falling back to pinepaper_get_items is acceptable but loses the triples representation.',
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
const ctx = app.exportCanvasOntology(${optsJson});
|
|
859
|
+
return {
|
|
860
|
+
success: true,
|
|
861
|
+
canvasWidth: ctx.canvasWidth,
|
|
862
|
+
canvasHeight: ctx.canvasHeight,
|
|
863
|
+
canvasPreset: ctx.canvasPreset,
|
|
864
|
+
itemCount: ctx.itemCount,
|
|
865
|
+
itemTypes: ctx.itemTypes,
|
|
866
|
+
hasAnimations: ctx.hasAnimations,
|
|
867
|
+
relationCount: ctx.relationCount,
|
|
868
|
+
items: ctx.items,
|
|
869
|
+
triples: ctx.triples,
|
|
870
|
+
viewport: ctx.viewport,
|
|
871
|
+
};
|
|
872
|
+
})();
|
|
751
873
|
`.trim();
|
|
752
874
|
}
|
|
753
875
|
/**
|
|
@@ -1062,13 +1184,9 @@ if (typeof app.generators !== 'undefined' && app.generators['${backgroundGenerat
|
|
|
1062
1184
|
const sourceId = nameToId['${source}'];
|
|
1063
1185
|
const targetId = nameToId['${target}'];
|
|
1064
1186
|
if (sourceId && targetId) {
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
const params = ${paramsStr};
|
|
1069
|
-
app.relationManager.addRelation(sourceItem, targetItem, '${type}', params);
|
|
1070
|
-
results.relations.push({ source: '${source}', target: '${target}', type: '${type}' });
|
|
1071
|
-
}
|
|
1187
|
+
const params = ${paramsStr};
|
|
1188
|
+
app.addRelation(sourceId, targetId, '${type}', params);
|
|
1189
|
+
results.relations.push({ source: '${source}', target: '${target}', type: '${type}' });
|
|
1072
1190
|
}
|
|
1073
1191
|
})();
|
|
1074
1192
|
`);
|
|
@@ -1346,10 +1464,11 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
1346
1464
|
*/
|
|
1347
1465
|
generateAgentStartJob(input) {
|
|
1348
1466
|
const validated = AgentStartJobInputSchema.parse(input);
|
|
1349
|
-
const { name, screenshotPolicy, canvasPreset, clearCanvas } = validated;
|
|
1467
|
+
const { name, screenshotPolicy, canvasPreset, clearCanvas, includeOntology } = validated;
|
|
1350
1468
|
const nameStr = name ? `'${name.replace(/'/g, "\\'")}'` : 'null';
|
|
1351
1469
|
const policyStr = screenshotPolicy || 'on_complete';
|
|
1352
1470
|
const shouldClear = clearCanvas !== false;
|
|
1471
|
+
const wantOntology = includeOntology !== false;
|
|
1353
1472
|
let code = `
|
|
1354
1473
|
// Start agent job
|
|
1355
1474
|
(async function() {
|
|
@@ -1378,10 +1497,31 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
1378
1497
|
app.setCanvasSize('${canvasPreset}');
|
|
1379
1498
|
// Allow canvas resize to take effect
|
|
1380
1499
|
await new Promise(r => setTimeout(r, 50));
|
|
1500
|
+
`;
|
|
1501
|
+
}
|
|
1502
|
+
if (wantOntology) {
|
|
1503
|
+
code += `
|
|
1504
|
+
// Capture initial canvas ontology so the agent has baseline context
|
|
1505
|
+
// without a follow-up pinepaper_get_canvas_ontology call. After
|
|
1506
|
+
// clearCanvas this is a tiny empty-state payload; with clearCanvas:false
|
|
1507
|
+
// it captures whatever's already on the canvas. Older FxTool builds
|
|
1508
|
+
// without exportCanvasOntology produce an _ontologyUnavailable: true
|
|
1509
|
+
// marker so the agent knows to fall back to pinepaper_get_items.
|
|
1510
|
+
let _ontology = null;
|
|
1511
|
+
let _ontologyUnavailable = false;
|
|
1512
|
+
try {
|
|
1513
|
+
if (app.exportCanvasOntology) {
|
|
1514
|
+
_ontology = app.exportCanvasOntology();
|
|
1515
|
+
} else {
|
|
1516
|
+
_ontologyUnavailable = true;
|
|
1517
|
+
}
|
|
1518
|
+
} catch (e) {
|
|
1519
|
+
_ontologyUnavailable = true;
|
|
1520
|
+
}
|
|
1381
1521
|
`;
|
|
1382
1522
|
}
|
|
1383
1523
|
code += `
|
|
1384
|
-
// Return job context with canvas size
|
|
1524
|
+
// Return job context with canvas size${wantOntology ? ' + initial ontology' : ''}
|
|
1385
1525
|
const _cs = app.getCanvasSize ? app.getCanvasSize() : { width: 800, height: 600 };
|
|
1386
1526
|
return {
|
|
1387
1527
|
success: true,
|
|
@@ -1390,7 +1530,9 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
1390
1530
|
screenshotPolicy: '${policyStr}',
|
|
1391
1531
|
canvasPreset: ${canvasPreset ? `'${canvasPreset}'` : 'null'},
|
|
1392
1532
|
canvasCleared: ${shouldClear},
|
|
1393
|
-
canvasSize: { width: _cs.width || 800, height: _cs.height || 600 }
|
|
1533
|
+
canvasSize: { width: _cs.width || 800, height: _cs.height || 600 }${wantOntology ? `,
|
|
1534
|
+
ontology: _ontology,
|
|
1535
|
+
ontologyUnavailable: _ontologyUnavailable || undefined` : ''}
|
|
1394
1536
|
};
|
|
1395
1537
|
})();
|
|
1396
1538
|
`;
|
|
@@ -1461,14 +1603,7 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
1461
1603
|
if (item.shadowColor || item.shadowBlur) analysis.hasShadows = true;
|
|
1462
1604
|
});
|
|
1463
1605
|
|
|
1464
|
-
//
|
|
1465
|
-
if (app.animationManager) {
|
|
1466
|
-
const animations = app.animationManager.getAll ? app.animationManager.getAll() : (app.animationManager.animations || []);
|
|
1467
|
-
if (animations.length > 0) {
|
|
1468
|
-
analysis.hasAnimations = true;
|
|
1469
|
-
animationSet.add('managed');
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1606
|
+
// FxTool has no animationManager — animations are detected via item.data.animationType walk above
|
|
1472
1607
|
// Check timeline for active animations
|
|
1473
1608
|
if (app.timeline && (app.timeline.isPlaying || app.timeline.animations?.length > 0)) {
|
|
1474
1609
|
analysis.hasAnimations = true;
|
|
@@ -1639,16 +1774,29 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
1639
1774
|
*/
|
|
1640
1775
|
generateBatchOperationCode(op, index) {
|
|
1641
1776
|
switch (op.type) {
|
|
1642
|
-
case 'create':
|
|
1777
|
+
case 'create': {
|
|
1643
1778
|
const pos = op.position || { x: 400, y: 300 };
|
|
1644
|
-
const
|
|
1645
|
-
|
|
1779
|
+
const createProps = (op.properties || {});
|
|
1780
|
+
const props = JSON.stringify(createProps);
|
|
1781
|
+
let createCode = `
|
|
1646
1782
|
const item = app.create('${op.itemType}', { position: { x: ${pos.x}, y: ${pos.y} }, ...${props} });
|
|
1647
|
-
const itemId = item.data && item.data.id ? item.data.id : app.registerItem(item, '${op.itemType}', { source: 'mcp-batch' })
|
|
1783
|
+
const itemId = item.data && item.data.id ? item.data.id : app.registerItem(item, '${op.itemType}', { source: 'mcp-batch' });`;
|
|
1784
|
+
// app.create() does not read blendMode/opacity from its params, but the
|
|
1785
|
+
// underlying Paper.js item supports them directly — apply post-create so
|
|
1786
|
+
// callers can pass them inline in `properties` and have them take effect.
|
|
1787
|
+
if (createProps.blendMode !== undefined) {
|
|
1788
|
+
createCode += `\nif ('blendMode' in item) item.blendMode = ${JSON.stringify(createProps.blendMode)};`;
|
|
1789
|
+
}
|
|
1790
|
+
if (createProps.opacity !== undefined) {
|
|
1791
|
+
createCode += `\nif ('opacity' in item) item.opacity = ${JSON.stringify(createProps.opacity)};`;
|
|
1792
|
+
}
|
|
1793
|
+
createCode += `
|
|
1648
1794
|
// Ensure item is visible above backgrounds/generators
|
|
1649
1795
|
if (item.bringToFront) item.bringToFront();
|
|
1650
1796
|
return { itemId };
|
|
1651
1797
|
`;
|
|
1798
|
+
return createCode;
|
|
1799
|
+
}
|
|
1652
1800
|
case 'modify':
|
|
1653
1801
|
const itemRef = op.itemId?.startsWith('$')
|
|
1654
1802
|
? `itemIds[${op.itemId.substring(1)}]`
|
|
@@ -1729,7 +1877,7 @@ return { success: true, width: ${w}, height: ${h} };
|
|
|
1729
1877
|
const kfItemRef = op.itemId?.startsWith('$')
|
|
1730
1878
|
? `itemIds[${op.itemId.substring(1)}]`
|
|
1731
1879
|
: `'${op.itemId}'`;
|
|
1732
|
-
const kfJson = JSON.stringify(op.keyframes || []);
|
|
1880
|
+
const kfJson = JSON.stringify(normalizeKeyframePositions(op.keyframes || []));
|
|
1733
1881
|
const kfDuration = op.duration || (op.keyframes?.length ? Math.max(...op.keyframes.map(k => k.time)) : 5);
|
|
1734
1882
|
const kfLoop = op.loop ?? false;
|
|
1735
1883
|
return `
|
|
@@ -1743,16 +1891,14 @@ return { itemId: targetId, duration: ${kfDuration}, loop: ${kfLoop} };
|
|
|
1743
1891
|
? `itemIds[${op.itemId.substring(1)}]`
|
|
1744
1892
|
: `'${op.itemId}'`;
|
|
1745
1893
|
const maskOpts = JSON.stringify(op.maskOptions || {});
|
|
1894
|
+
const preset = op.maskPreset || op.maskType || 'wipeLeft';
|
|
1746
1895
|
return `
|
|
1747
1896
|
const targetId = ${maskItemRef};
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
maskOptions: ${maskOpts}
|
|
1754
|
-
});
|
|
1755
|
-
return result;
|
|
1897
|
+
const item = app.getItemById(targetId);
|
|
1898
|
+
if (!item) throw new Error('Item not found: ' + targetId);
|
|
1899
|
+
if (!app.applyAnimatedMask) throw new Error('Mask system not available');
|
|
1900
|
+
const maskedGroup = app.applyAnimatedMask(item, '${preset}', ${maskOpts});
|
|
1901
|
+
return { success: !!maskedGroup, itemId: targetId, preset: '${preset}' };
|
|
1756
1902
|
`;
|
|
1757
1903
|
}
|
|
1758
1904
|
case 'apply_effect': {
|
|
@@ -1800,8 +1946,9 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
1800
1946
|
*/
|
|
1801
1947
|
generateAgentExport(input) {
|
|
1802
1948
|
const validated = AgentExportInputSchema.parse(input);
|
|
1803
|
-
const { platform, format, quality } = validated;
|
|
1949
|
+
const { platform, format, quality, framing, duration } = validated;
|
|
1804
1950
|
const qualityLevel = quality || 'standard';
|
|
1951
|
+
const videoDuration = duration ?? 5;
|
|
1805
1952
|
// Quality settings (bitrate in bps for VideoEncoder)
|
|
1806
1953
|
const qualitySettings = {
|
|
1807
1954
|
draft: { compression: 0.6, fps: 15, dpi: 72, bitrate: 2_000_000 },
|
|
@@ -1830,10 +1977,38 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
1830
1977
|
const platform = '${platform}';
|
|
1831
1978
|
const format = '${exportFormat}';
|
|
1832
1979
|
const quality = '${qualityLevel}';
|
|
1980
|
+
const framing = '${framing}';
|
|
1833
1981
|
const settings = ${JSON.stringify(qualitySettings)};
|
|
1834
1982
|
const dimensions = ${JSON.stringify({ width: preset.width, height: preset.height })};
|
|
1835
1983
|
|
|
1836
|
-
|
|
1984
|
+
// Resolve camera-view framing dims when requested. Reads the first
|
|
1985
|
+
// camera_animates keyframe's zoom and divides the canvas dims by it
|
|
1986
|
+
// (mirrors FxTool ExportEngine.js:466-485). Camera animation still
|
|
1987
|
+
// drives motion during export — framing only fixes the output frame.
|
|
1988
|
+
let cameraDims = null;
|
|
1989
|
+
if (framing === 'camera') {
|
|
1990
|
+
if (!['gif', 'mp4', 'webm'].includes(format)) {
|
|
1991
|
+
return { success: false, platform, format, framing, error: 'framing: "camera" is only supported for video formats (gif, mp4, webm). Use format: "mp4" / "gif" / "webm" or omit framing.' };
|
|
1992
|
+
}
|
|
1993
|
+
const rr = app.relationRegistry;
|
|
1994
|
+
const hasWalkthrough = rr && typeof rr.hasCameraAnimation === 'function' && rr.hasCameraAnimation();
|
|
1995
|
+
if (!hasWalkthrough) {
|
|
1996
|
+
return { success: false, platform, format, framing, error: 'framing: "camera" requires a camera_animates walkthrough on the canvas. Add one or omit framing.' };
|
|
1997
|
+
}
|
|
1998
|
+
const params = rr.getCameraAnimationParams ? rr.getCameraAnimationParams() : null;
|
|
1999
|
+
if (!params || (params.mode || 'keyframes') !== 'keyframes' || !params.keyframes || !params.keyframes.length) {
|
|
2000
|
+
return { success: false, platform, format, framing, error: 'framing: "camera" requires a keyframe-mode camera walkthrough with at least one keyframe.' };
|
|
2001
|
+
}
|
|
2002
|
+
const firstKf = params.keyframes[0];
|
|
2003
|
+
const firstZoom = firstKf && firstKf.zoom > 0 ? firstKf.zoom : 1;
|
|
2004
|
+
const canvasEl = document.querySelector('canvas');
|
|
2005
|
+
cameraDims = {
|
|
2006
|
+
width: Math.round((canvasEl ? canvasEl.width : dimensions.width) / firstZoom),
|
|
2007
|
+
height: Math.round((canvasEl ? canvasEl.height : dimensions.height) / firstZoom),
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
let result = { success: false, platform, format, quality, framing };
|
|
1837
2012
|
|
|
1838
2013
|
try {
|
|
1839
2014
|
switch (format) {
|
|
@@ -1881,28 +2056,44 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
1881
2056
|
case 'mp4':
|
|
1882
2057
|
case 'webm': {
|
|
1883
2058
|
const videoMimeType = { mp4: 'video/mp4', webm: 'video/webm', gif: 'image/gif' }[format];
|
|
1884
|
-
//
|
|
1885
|
-
|
|
1886
|
-
|
|
2059
|
+
// FxTool VideoExporter._calculateBitrate multiplies a per-resolution
|
|
2060
|
+
// base by settings.quality (0.5-1.0). Our existing per-tier compression
|
|
2061
|
+
// values (0.6 / 0.85 / 0.95) map directly to that scale. Without this,
|
|
2062
|
+
// ExportEngine._quickExportVideo overrides quality with undefined for
|
|
2063
|
+
// mp4/webm, _calculateBitrate yields NaN, and VideoEncoder.configure
|
|
2064
|
+
// rejects with 'malformed bitrate value'.
|
|
2065
|
+
const baseVideoSettings = { format, fps: settings.fps, quality: settings.compression, duration: ${videoDuration} };
|
|
2066
|
+
const blobToDataUrl = (b) => new Promise(resolve => {
|
|
2067
|
+
const r = new FileReader();
|
|
2068
|
+
r.onloadend = () => resolve(r.result);
|
|
2069
|
+
r.readAsDataURL(b);
|
|
2070
|
+
});
|
|
2071
|
+
// Camera framing requires going direct to videoExporter so width/height
|
|
2072
|
+
// pass through — _quickExportVideo strips dim fields.
|
|
2073
|
+
if (cameraDims && app.exportEngine && app.exportEngine.videoExporter) {
|
|
2074
|
+
const blob = await app.exportEngine.videoExporter.export({ ...baseVideoSettings, width: cameraDims.width, height: cameraDims.height });
|
|
2075
|
+
const dataUrl = await blobToDataUrl(blob);
|
|
2076
|
+
result = { success: true, platform, format, framing, data: dataUrl, mimeType: videoMimeType, size: blob.size, dimensions: cameraDims };
|
|
2077
|
+
} else if ((format === 'mp4' || format === 'webm') && app.exportEngine && app.exportEngine.videoExporter) {
|
|
2078
|
+
// Bypass _quickExportVideo for mp4/webm — it hardcodes
|
|
2079
|
+
// quality:undefined for these formats and produces a NaN bitrate.
|
|
2080
|
+
const blob = await app.exportEngine.videoExporter.export(baseVideoSettings);
|
|
2081
|
+
const dataUrl = await blobToDataUrl(blob);
|
|
2082
|
+
result = { success: true, platform, format, framing, data: dataUrl, mimeType: videoMimeType, size: blob.size };
|
|
2083
|
+
} else if (app.exportEngine && app.exportEngine._quickExportVideo) {
|
|
2084
|
+
// GIF path (working): _quickExportVideo forwards gifQuality to
|
|
2085
|
+
// gif.js. Also serves as the fallback if videoExporter is absent.
|
|
2086
|
+
const videoResult = await app.exportEngine._quickExportVideo(format, baseVideoSettings, false);
|
|
1887
2087
|
if (videoResult && videoResult.blob) {
|
|
1888
|
-
const
|
|
1889
|
-
|
|
1890
|
-
reader.onloadend = () => resolve(reader.result);
|
|
1891
|
-
reader.readAsDataURL(videoResult.blob);
|
|
1892
|
-
});
|
|
1893
|
-
result = { success: true, platform, format, data: dataUrl, mimeType: videoMimeType, size: videoResult.blob.size };
|
|
2088
|
+
const dataUrl = await blobToDataUrl(videoResult.blob);
|
|
2089
|
+
result = { success: true, platform, format, framing, data: dataUrl, mimeType: videoMimeType, size: videoResult.blob.size };
|
|
1894
2090
|
} else {
|
|
1895
2091
|
result = { success: false, error: format.toUpperCase() + ' export returned no data' };
|
|
1896
2092
|
}
|
|
1897
2093
|
} else if (app.exportEngine && app.exportEngine.videoExporter) {
|
|
1898
|
-
|
|
1899
|
-
const
|
|
1900
|
-
|
|
1901
|
-
const dataUrl = await new Promise(resolve => {
|
|
1902
|
-
reader.onloadend = () => resolve(reader.result);
|
|
1903
|
-
reader.readAsDataURL(blob);
|
|
1904
|
-
});
|
|
1905
|
-
result = { success: true, platform, format, data: dataUrl, mimeType: videoMimeType, size: blob.size };
|
|
2094
|
+
const blob = await app.exportEngine.videoExporter.export(baseVideoSettings);
|
|
2095
|
+
const dataUrl = await blobToDataUrl(blob);
|
|
2096
|
+
result = { success: true, platform, format, framing, data: dataUrl, mimeType: videoMimeType, size: blob.size };
|
|
1906
2097
|
} else {
|
|
1907
2098
|
result = { success: false, error: format.toUpperCase() + ' export not available' };
|
|
1908
2099
|
}
|
|
@@ -1994,14 +2185,7 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
1994
2185
|
if (item.shadowColor || item.shadowBlur) hasShadow = true;
|
|
1995
2186
|
});
|
|
1996
2187
|
|
|
1997
|
-
//
|
|
1998
|
-
if (app.animationManager) {
|
|
1999
|
-
const animations = app.animationManager.getAll ? app.animationManager.getAll() : (app.animationManager.animations || []);
|
|
2000
|
-
if (animations.length > 0) {
|
|
2001
|
-
analysis.hasAnimations = true;
|
|
2002
|
-
animationSet.add('managed');
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2188
|
+
// FxTool has no animationManager — animations are detected via item.data.animationType walk above
|
|
2005
2189
|
// Check timeline for active animations
|
|
2006
2190
|
if (app.timeline && (app.timeline.isPlaying || app.timeline.animations?.length > 0)) {
|
|
2007
2191
|
analysis.hasAnimations = true;
|
|
@@ -2065,257 +2249,6 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
2065
2249
|
suggestedPlatforms
|
|
2066
2250
|
};
|
|
2067
2251
|
})();
|
|
2068
|
-
`.trim();
|
|
2069
|
-
}
|
|
2070
|
-
// ===========================================================================
|
|
2071
|
-
// INTERACTIVE TRIGGER CODE GENERATORS
|
|
2072
|
-
// ===========================================================================
|
|
2073
|
-
/**
|
|
2074
|
-
* Generate code for adding a trigger
|
|
2075
|
-
*/
|
|
2076
|
-
generateAddTrigger(input) {
|
|
2077
|
-
const validated = AddTriggerInputSchema.parse(input);
|
|
2078
|
-
const { itemId, event, actions, condition, timelineOffset } = validated;
|
|
2079
|
-
const actionsStr = JSON.stringify(actions);
|
|
2080
|
-
const conditionStr = condition ? `'${condition.replace(/'/g, "\\'")}'` : 'null';
|
|
2081
|
-
const offsetStr = timelineOffset !== undefined ? timelineOffset : 'null';
|
|
2082
|
-
return `
|
|
2083
|
-
// Add trigger to ${itemId}
|
|
2084
|
-
const item = app.getItemById('${itemId}');
|
|
2085
|
-
if (!item) throw new Error('Item not found: ${itemId}');
|
|
2086
|
-
|
|
2087
|
-
const trigger = {
|
|
2088
|
-
event: '${event}',
|
|
2089
|
-
actions: ${actionsStr},
|
|
2090
|
-
condition: ${conditionStr},
|
|
2091
|
-
timelineOffset: ${offsetStr}
|
|
2092
|
-
};
|
|
2093
|
-
|
|
2094
|
-
// Initialize triggers array if needed
|
|
2095
|
-
if (!item.data) item.data = {};
|
|
2096
|
-
if (!item.data.triggers) item.data.triggers = [];
|
|
2097
|
-
|
|
2098
|
-
// Add trigger
|
|
2099
|
-
item.data.triggers.push(trigger);
|
|
2100
|
-
|
|
2101
|
-
// Register event handler
|
|
2102
|
-
if (app.triggerManager) {
|
|
2103
|
-
app.triggerManager.registerTrigger(item, trigger);
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
app.historyManager.saveState();
|
|
2107
|
-
({ success: true, itemId: '${itemId}', event: '${event}', triggerId: item.data.triggers.length - 1 });
|
|
2108
|
-
`.trim();
|
|
2109
|
-
}
|
|
2110
|
-
/**
|
|
2111
|
-
* Generate code for removing a trigger
|
|
2112
|
-
*/
|
|
2113
|
-
generateRemoveTrigger(input) {
|
|
2114
|
-
const validated = RemoveTriggerInputSchema.parse(input);
|
|
2115
|
-
const { itemId, event, removeAll } = validated;
|
|
2116
|
-
if (removeAll) {
|
|
2117
|
-
return `
|
|
2118
|
-
// Remove all triggers from ${itemId}
|
|
2119
|
-
const item = app.getItemById('${itemId}');
|
|
2120
|
-
if (!item) throw new Error('Item not found: ${itemId}');
|
|
2121
|
-
|
|
2122
|
-
const removedCount = item.data?.triggers?.length || 0;
|
|
2123
|
-
if (item.data) item.data.triggers = [];
|
|
2124
|
-
|
|
2125
|
-
if (app.triggerManager) {
|
|
2126
|
-
app.triggerManager.unregisterAll(item);
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
app.historyManager.saveState();
|
|
2130
|
-
({ success: true, itemId: '${itemId}', removedCount, removeAll: true });
|
|
2131
|
-
`.trim();
|
|
2132
|
-
}
|
|
2133
|
-
return `
|
|
2134
|
-
// Remove ${event} trigger from ${itemId}
|
|
2135
|
-
const item = app.getItemById('${itemId}');
|
|
2136
|
-
if (!item) throw new Error('Item not found: ${itemId}');
|
|
2137
|
-
|
|
2138
|
-
let removedCount = 0;
|
|
2139
|
-
if (item.data?.triggers) {
|
|
2140
|
-
const before = item.data.triggers.length;
|
|
2141
|
-
item.data.triggers = item.data.triggers.filter(t => t.event !== '${event}');
|
|
2142
|
-
removedCount = before - item.data.triggers.length;
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
|
-
if (app.triggerManager) {
|
|
2146
|
-
app.triggerManager.unregisterEvent(item, '${event}');
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
app.historyManager.saveState();
|
|
2150
|
-
({ success: true, itemId: '${itemId}', event: '${event}', removedCount });
|
|
2151
|
-
`.trim();
|
|
2152
|
-
}
|
|
2153
|
-
/**
|
|
2154
|
-
* Generate code for querying triggers
|
|
2155
|
-
*/
|
|
2156
|
-
generateQueryTriggers(input) {
|
|
2157
|
-
const validated = QueryTriggersInputSchema.parse(input);
|
|
2158
|
-
const { itemId, event } = validated;
|
|
2159
|
-
if (itemId) {
|
|
2160
|
-
return `
|
|
2161
|
-
// Query triggers for ${itemId}
|
|
2162
|
-
const item = app.getItemById('${itemId}');
|
|
2163
|
-
if (!item) throw new Error('Item not found: ${itemId}');
|
|
2164
|
-
|
|
2165
|
-
let triggers = item.data?.triggers || [];
|
|
2166
|
-
${event ? `triggers = triggers.filter(t => t.event === '${event}');` : ''}
|
|
2167
|
-
|
|
2168
|
-
({ success: true, itemId: '${itemId}', triggers, count: triggers.length });
|
|
2169
|
-
`.trim();
|
|
2170
|
-
}
|
|
2171
|
-
return `
|
|
2172
|
-
// Query all triggers
|
|
2173
|
-
const allTriggers = [];
|
|
2174
|
-
const items = app.itemRegistry ? app.itemRegistry.getAll() : [];
|
|
2175
|
-
|
|
2176
|
-
items.forEach(entry => {
|
|
2177
|
-
const item = entry.item;
|
|
2178
|
-
if (item.data?.triggers?.length > 0) {
|
|
2179
|
-
let triggers = item.data.triggers;
|
|
2180
|
-
${event ? `triggers = triggers.filter(t => t.event === '${event}');` : ''}
|
|
2181
|
-
triggers.forEach((trigger, idx) => {
|
|
2182
|
-
allTriggers.push({
|
|
2183
|
-
itemId: entry.id,
|
|
2184
|
-
triggerId: idx,
|
|
2185
|
-
...trigger
|
|
2186
|
-
});
|
|
2187
|
-
});
|
|
2188
|
-
}
|
|
2189
|
-
});
|
|
2190
|
-
|
|
2191
|
-
({ success: true, triggers: allTriggers, count: allTriggers.length${event ? `, event: '${event}'` : ''} });
|
|
2192
|
-
`.trim();
|
|
2193
|
-
}
|
|
2194
|
-
// ===========================================================================
|
|
2195
|
-
// QUIZ CODE GENERATORS
|
|
2196
|
-
// ===========================================================================
|
|
2197
|
-
/**
|
|
2198
|
-
* Generate code for creating a quiz
|
|
2199
|
-
*/
|
|
2200
|
-
generateCreateQuiz(input) {
|
|
2201
|
-
const validated = CreateQuizInputSchema.parse(input);
|
|
2202
|
-
const { title, questions, passingScore, showScore, allowRetry, shuffleQuestions, shuffleOptions } = validated;
|
|
2203
|
-
const quizConfig = JSON.stringify({
|
|
2204
|
-
title: title || 'Untitled Quiz',
|
|
2205
|
-
questions,
|
|
2206
|
-
passingScore: passingScore || 70,
|
|
2207
|
-
showScore: showScore !== false,
|
|
2208
|
-
allowRetry: allowRetry !== false,
|
|
2209
|
-
shuffleQuestions: shuffleQuestions || false,
|
|
2210
|
-
shuffleOptions: shuffleOptions || false,
|
|
2211
|
-
}, null, 2);
|
|
2212
|
-
return `
|
|
2213
|
-
// Create quiz
|
|
2214
|
-
const quizConfig = ${quizConfig};
|
|
2215
|
-
|
|
2216
|
-
// Initialize quiz manager if needed
|
|
2217
|
-
if (!app.quizManager) {
|
|
2218
|
-
app.quizManager = {
|
|
2219
|
-
quizzes: [],
|
|
2220
|
-
activeQuiz: null,
|
|
2221
|
-
createQuiz: function(config) {
|
|
2222
|
-
const quiz = {
|
|
2223
|
-
id: 'quiz_' + Date.now(),
|
|
2224
|
-
...config,
|
|
2225
|
-
state: {
|
|
2226
|
-
currentQuestion: 0,
|
|
2227
|
-
answers: [],
|
|
2228
|
-
score: 0,
|
|
2229
|
-
maxScore: config.questions.reduce((sum, q) => sum + q.points, 0),
|
|
2230
|
-
complete: false
|
|
2231
|
-
}
|
|
2232
|
-
};
|
|
2233
|
-
this.quizzes.push(quiz);
|
|
2234
|
-
this.activeQuiz = quiz;
|
|
2235
|
-
return quiz;
|
|
2236
|
-
}
|
|
2237
|
-
};
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
const quiz = app.quizManager.createQuiz(quizConfig);
|
|
2241
|
-
app.historyManager.saveState();
|
|
2242
|
-
|
|
2243
|
-
({
|
|
2244
|
-
success: true,
|
|
2245
|
-
quizId: quiz.id,
|
|
2246
|
-
title: quiz.title,
|
|
2247
|
-
questionCount: quiz.questions.length,
|
|
2248
|
-
maxScore: quiz.state.maxScore,
|
|
2249
|
-
passingScore: quiz.passingScore
|
|
2250
|
-
});
|
|
2251
|
-
`.trim();
|
|
2252
|
-
}
|
|
2253
|
-
/**
|
|
2254
|
-
* Generate code for getting quiz state
|
|
2255
|
-
*/
|
|
2256
|
-
generateGetQuizState(input) {
|
|
2257
|
-
const validated = GetQuizStateInputSchema.parse(input);
|
|
2258
|
-
const { quizId } = validated;
|
|
2259
|
-
const quizRef = quizId
|
|
2260
|
-
? `app.quizManager.quizzes.find(q => q.id === '${quizId}')`
|
|
2261
|
-
: 'app.quizManager?.activeQuiz';
|
|
2262
|
-
return `
|
|
2263
|
-
// Get quiz state
|
|
2264
|
-
if (!app.quizManager) {
|
|
2265
|
-
return { success: false, error: 'No quiz manager initialized' };
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2268
|
-
const quiz = ${quizRef};
|
|
2269
|
-
if (!quiz) {
|
|
2270
|
-
return { success: false, error: 'Quiz not found' };
|
|
2271
|
-
}
|
|
2272
|
-
|
|
2273
|
-
({
|
|
2274
|
-
success: true,
|
|
2275
|
-
quizId: quiz.id,
|
|
2276
|
-
title: quiz.title,
|
|
2277
|
-
currentQuestion: quiz.state.currentQuestion,
|
|
2278
|
-
totalQuestions: quiz.questions.length,
|
|
2279
|
-
score: quiz.state.score,
|
|
2280
|
-
maxScore: quiz.state.maxScore,
|
|
2281
|
-
answers: quiz.state.answers,
|
|
2282
|
-
complete: quiz.state.complete,
|
|
2283
|
-
passed: quiz.state.complete ? (quiz.state.score / quiz.state.maxScore * 100) >= quiz.passingScore : null
|
|
2284
|
-
});
|
|
2285
|
-
`.trim();
|
|
2286
|
-
}
|
|
2287
|
-
/**
|
|
2288
|
-
* Generate code for resetting a quiz
|
|
2289
|
-
*/
|
|
2290
|
-
generateResetQuiz(input) {
|
|
2291
|
-
const validated = ResetQuizInputSchema.parse(input);
|
|
2292
|
-
const { quizId } = validated;
|
|
2293
|
-
const quizRef = quizId
|
|
2294
|
-
? `app.quizManager.quizzes.find(q => q.id === '${quizId}')`
|
|
2295
|
-
: 'app.quizManager?.activeQuiz';
|
|
2296
|
-
return `
|
|
2297
|
-
// Reset quiz
|
|
2298
|
-
if (!app.quizManager) {
|
|
2299
|
-
return { success: false, error: 'No quiz manager initialized' };
|
|
2300
|
-
}
|
|
2301
|
-
|
|
2302
|
-
const quiz = ${quizRef};
|
|
2303
|
-
if (!quiz) {
|
|
2304
|
-
return { success: false, error: 'Quiz not found' };
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
// Reset state
|
|
2308
|
-
quiz.state = {
|
|
2309
|
-
currentQuestion: 0,
|
|
2310
|
-
answers: [],
|
|
2311
|
-
score: 0,
|
|
2312
|
-
maxScore: quiz.questions.reduce((sum, q) => sum + q.points, 0),
|
|
2313
|
-
complete: false
|
|
2314
|
-
};
|
|
2315
|
-
|
|
2316
|
-
app.historyManager.saveState();
|
|
2317
|
-
|
|
2318
|
-
({ success: true, quizId: quiz.id, reset: true });
|
|
2319
2252
|
`.trim();
|
|
2320
2253
|
}
|
|
2321
2254
|
// ===========================================================================
|
|
@@ -2345,7 +2278,7 @@ app.historyManager.saveState();
|
|
|
2345
2278
|
if (gradientDirection)
|
|
2346
2279
|
options.gradientDirection = gradientDirection;
|
|
2347
2280
|
return `
|
|
2348
|
-
// Create letter collage
|
|
2281
|
+
// Create letter collage
|
|
2349
2282
|
(function() {
|
|
2350
2283
|
const text = ${JSON.stringify(text)};
|
|
2351
2284
|
const options = ${JSON.stringify(options, null, 2)};
|
|
@@ -2360,7 +2293,21 @@ app.historyManager.saveState();
|
|
|
2360
2293
|
app.historyManager.saveState('create_letter_collage');
|
|
2361
2294
|
}
|
|
2362
2295
|
|
|
2363
|
-
|
|
2296
|
+
// Return a SERIALIZABLE summary — never the raw result. result.group and
|
|
2297
|
+
// result.letters are live Paper.js objects (circular parent/children refs,
|
|
2298
|
+
// methods, canvas handles); the browser round-trip (page.evaluate / CDP
|
|
2299
|
+
// return-by-value) can't serialize them, so returning result throws
|
|
2300
|
+
// "Failed to execute code in browser" even though the collage was already
|
|
2301
|
+
// created — a false negative. Surface registry ids instead.
|
|
2302
|
+
const collageId = (result && (result.collageId
|
|
2303
|
+
|| (result.group && result.group.data && (result.group.data.id || result.group.data.registryId)))) || null;
|
|
2304
|
+
const letterIds = (result && Array.isArray(result.letters))
|
|
2305
|
+
? result.letters.map(function(l){ return (l && l.data && (l.data.id || l.data.registryId)) || null; }).filter(Boolean)
|
|
2306
|
+
: [];
|
|
2307
|
+
if (!collageId) {
|
|
2308
|
+
return { success: false, error: 'createLetterCollage returned no id', letterCount: letterIds.length, letterIds: letterIds };
|
|
2309
|
+
}
|
|
2310
|
+
return { success: true, collageId: collageId, itemId: collageId, letterCount: letterIds.length, letterIds: letterIds };
|
|
2364
2311
|
})();
|
|
2365
2312
|
`.trim();
|
|
2366
2313
|
}
|
|
@@ -2390,80 +2337,6 @@ app.historyManager.saveState();
|
|
|
2390
2337
|
|
|
2391
2338
|
return result;
|
|
2392
2339
|
})();
|
|
2393
|
-
`.trim();
|
|
2394
|
-
}
|
|
2395
|
-
/**
|
|
2396
|
-
* Generate code to get letter collage options
|
|
2397
|
-
*/
|
|
2398
|
-
generateGetLetterCollageOptions() {
|
|
2399
|
-
return `
|
|
2400
|
-
// Get letter collage options
|
|
2401
|
-
(function() {
|
|
2402
|
-
if (!app.getLetterCollageOptions) {
|
|
2403
|
-
// Return static options if function not available
|
|
2404
|
-
return {
|
|
2405
|
-
success: true,
|
|
2406
|
-
styles: ['tile', 'magazine', 'paperCut', 'fold', 'gradient', 'image'],
|
|
2407
|
-
tilePalettes: {
|
|
2408
|
-
game: ['wordle', 'scrabble'],
|
|
2409
|
-
vibrant: ['candy', 'neon', 'rainbow'],
|
|
2410
|
-
soft: ['pastel', 'cotton'],
|
|
2411
|
-
natural: ['earth', 'ocean', 'forest', 'sunset'],
|
|
2412
|
-
professional: ['corporate', 'minimal', 'slate'],
|
|
2413
|
-
seasonal: ['christmas', 'halloween', 'spring'],
|
|
2414
|
-
magazine: ['magazine', 'newspaper', 'vintage'],
|
|
2415
|
-
paperCraft: ['paperCraft', 'origami', 'craftPaper']
|
|
2416
|
-
},
|
|
2417
|
-
gradientPalettes: ['rainbow', 'sunset', 'ocean', 'fire', 'gold', 'rose', 'ice', 'cyberpunk', 'neonGlow', 'purplePink'],
|
|
2418
|
-
gradientDirections: ['vertical', 'horizontal', 'diagonal', 'radial']
|
|
2419
|
-
};
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
|
-
return app.getLetterCollageOptions();
|
|
2423
|
-
})();
|
|
2424
|
-
`.trim();
|
|
2425
|
-
}
|
|
2426
|
-
// ===========================================================================
|
|
2427
|
-
// CANVAS PRESETS CODE GENERATORS
|
|
2428
|
-
// ===========================================================================
|
|
2429
|
-
/**
|
|
2430
|
-
* Generate code to get canvas presets
|
|
2431
|
-
*/
|
|
2432
|
-
generateGetCanvasPresets() {
|
|
2433
|
-
return `
|
|
2434
|
-
// Get canvas presets
|
|
2435
|
-
(function() {
|
|
2436
|
-
if (app.getCanvasPresets) {
|
|
2437
|
-
return app.getCanvasPresets();
|
|
2438
|
-
}
|
|
2439
|
-
|
|
2440
|
-
// Return static presets if function not available
|
|
2441
|
-
return {
|
|
2442
|
-
success: true,
|
|
2443
|
-
presets: [
|
|
2444
|
-
{ key: 'youtube-thumbnail', name: 'YouTube Thumbnail', width: 1280, height: 720, aspectRatio: '16:9', category: 'video' },
|
|
2445
|
-
{ key: 'youtube-short', name: 'YouTube Shorts', width: 1080, height: 1920, aspectRatio: '9:16', category: 'video' },
|
|
2446
|
-
{ key: 'tiktok', name: 'TikTok', width: 1080, height: 1920, aspectRatio: '9:16', category: 'video' },
|
|
2447
|
-
{ key: 'instagram-story', name: 'Instagram Story', width: 1080, height: 1920, aspectRatio: '9:16', category: 'social' },
|
|
2448
|
-
{ key: 'instagram-post', name: 'Instagram Post (Square)', width: 1080, height: 1080, aspectRatio: '1:1', category: 'social' },
|
|
2449
|
-
{ key: 'instagram-landscape', name: 'Instagram Landscape', width: 1080, height: 566, aspectRatio: '1.91:1', category: 'social' },
|
|
2450
|
-
{ key: 'instagram-portrait', name: 'Instagram Portrait', width: 1080, height: 1350, aspectRatio: '4:5', category: 'social' },
|
|
2451
|
-
{ key: 'facebook-post', name: 'Facebook Post', width: 1200, height: 630, aspectRatio: '1.91:1', category: 'social' },
|
|
2452
|
-
{ key: 'facebook-cover', name: 'Facebook Cover', width: 820, height: 312, aspectRatio: '2.63:1', category: 'social' },
|
|
2453
|
-
{ key: 'facebook-story', name: 'Facebook Story', width: 1080, height: 1920, aspectRatio: '9:16', category: 'social' },
|
|
2454
|
-
{ key: 'twitter-post', name: 'Twitter/X Post', width: 1200, height: 675, aspectRatio: '16:9', category: 'social' },
|
|
2455
|
-
{ key: 'twitter-header', name: 'Twitter/X Header', width: 1500, height: 500, aspectRatio: '3:1', category: 'social' },
|
|
2456
|
-
{ key: 'linkedin-post', name: 'LinkedIn Post', width: 1200, height: 627, aspectRatio: '1.91:1', category: 'social' },
|
|
2457
|
-
{ key: 'linkedin-banner', name: 'LinkedIn Banner', width: 1584, height: 396, aspectRatio: '4:1', category: 'social' },
|
|
2458
|
-
{ key: 'pinterest-pin', name: 'Pinterest Pin', width: 1000, height: 1500, aspectRatio: '2:3', category: 'social' },
|
|
2459
|
-
{ key: 'presentation-16x9', name: 'Presentation 16:9', width: 1920, height: 1080, aspectRatio: '16:9', category: 'presentation' },
|
|
2460
|
-
{ key: 'presentation-4x3', name: 'Presentation 4:3', width: 1024, height: 768, aspectRatio: '4:3', category: 'presentation' },
|
|
2461
|
-
{ key: 'hd-720p', name: 'HD 720p', width: 1280, height: 720, aspectRatio: '16:9', category: 'video' },
|
|
2462
|
-
{ key: 'full-hd-1080p', name: 'Full HD 1080p', width: 1920, height: 1080, aspectRatio: '16:9', category: 'video' },
|
|
2463
|
-
{ key: 'default', name: 'Default', width: 800, height: 600, aspectRatio: '4:3', category: 'general' }
|
|
2464
|
-
]
|
|
2465
|
-
};
|
|
2466
|
-
})();
|
|
2467
2340
|
`.trim();
|
|
2468
2341
|
}
|
|
2469
2342
|
// ===========================================================================
|
|
@@ -3248,22 +3121,27 @@ app.historyManager.saveState();
|
|
|
3248
3121
|
const optionsStr = validated.options ? JSON.stringify(validated.options) : '{}';
|
|
3249
3122
|
const keyframesStr = validated.keyframes ? JSON.stringify(validated.keyframes) : 'null';
|
|
3250
3123
|
const maskOptionsStr = validated.maskOptions ? JSON.stringify(validated.maskOptions) : '{}';
|
|
3124
|
+
const preset = validated.preset || validated.maskType || 'wipeLeft';
|
|
3125
|
+
// Per-preset options. Custom keyframes are an animated-mask extension —
|
|
3126
|
+
// when supplied, fall through to maskingSystem.applyCustomMask instead.
|
|
3251
3127
|
return `
|
|
3252
3128
|
// Apply animated mask to ${validated.itemId}
|
|
3253
3129
|
(function() {
|
|
3254
|
-
if (!app.
|
|
3130
|
+
if (!app.applyAnimatedMask) {
|
|
3255
3131
|
return { success: false, error: 'Mask system not available' };
|
|
3256
3132
|
}
|
|
3133
|
+
const item = app.getItemById('${validated.itemId}');
|
|
3134
|
+
if (!item) return { success: false, error: 'Item not found: ${validated.itemId}' };
|
|
3257
3135
|
|
|
3258
3136
|
try {
|
|
3259
|
-
const
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
}
|
|
3266
|
-
return
|
|
3137
|
+
const mergedOpts = Object.assign({}, ${optionsStr}, ${maskOptionsStr});
|
|
3138
|
+
let maskedGroup;
|
|
3139
|
+
if (${keyframesStr} && app.maskingSystem && app.maskingSystem.applyCustomMask) {
|
|
3140
|
+
maskedGroup = app.maskingSystem.applyCustomMask(item, '${preset}', ${keyframesStr}, mergedOpts);
|
|
3141
|
+
} else {
|
|
3142
|
+
maskedGroup = app.applyAnimatedMask(item, '${preset}', mergedOpts);
|
|
3143
|
+
}
|
|
3144
|
+
return { success: !!maskedGroup, itemId: '${validated.itemId}', preset: '${preset}' };
|
|
3267
3145
|
} catch (error) {
|
|
3268
3146
|
return { success: false, error: error.message };
|
|
3269
3147
|
}
|
|
@@ -3277,17 +3155,15 @@ app.historyManager.saveState();
|
|
|
3277
3155
|
return `
|
|
3278
3156
|
// Apply custom mask to ${validated.itemId}
|
|
3279
3157
|
(function() {
|
|
3280
|
-
if (!app.
|
|
3158
|
+
if (!app.maskingSystem || !app.maskingSystem.applyCustomMask) {
|
|
3281
3159
|
return { success: false, error: 'Mask system not available' };
|
|
3282
3160
|
}
|
|
3161
|
+
const item = app.getItemById('${validated.itemId}');
|
|
3162
|
+
if (!item) return { success: false, error: 'Item not found: ${validated.itemId}' };
|
|
3283
3163
|
|
|
3284
3164
|
try {
|
|
3285
|
-
const
|
|
3286
|
-
|
|
3287
|
-
keyframes: ${keyframesStr},
|
|
3288
|
-
maskOptions: ${maskOptionsStr}
|
|
3289
|
-
});
|
|
3290
|
-
return result;
|
|
3165
|
+
const maskedGroup = app.maskingSystem.applyCustomMask(item, '${validated.maskType}', ${keyframesStr}, ${maskOptionsStr});
|
|
3166
|
+
return { success: !!maskedGroup, itemId: '${validated.itemId}', maskType: '${validated.maskType}' };
|
|
3291
3167
|
} catch (error) {
|
|
3292
3168
|
return { success: false, error: error.message };
|
|
3293
3169
|
}
|
|
@@ -3299,12 +3175,17 @@ app.historyManager.saveState();
|
|
|
3299
3175
|
return `
|
|
3300
3176
|
// Remove mask from ${validated.itemId}
|
|
3301
3177
|
(function() {
|
|
3302
|
-
if (!app.
|
|
3178
|
+
if (!app.removeMask) {
|
|
3303
3179
|
return { success: false, error: 'Mask system not available' };
|
|
3304
3180
|
}
|
|
3181
|
+
// The item id may point at either the masked group OR the original item;
|
|
3182
|
+
// app.removeMask expects the masked group, so resolve and walk up if needed.
|
|
3183
|
+
const item = app.getItemById('${validated.itemId}');
|
|
3184
|
+
if (!item) return { success: false, error: 'Item not found: ${validated.itemId}' };
|
|
3185
|
+
const maskedGroup = (item.data && item.data.isMaskedGroup) ? item : (item.parent && item.parent.data && item.parent.data.isMaskedGroup ? item.parent : item);
|
|
3305
3186
|
|
|
3306
3187
|
try {
|
|
3307
|
-
|
|
3188
|
+
app.removeMask(maskedGroup);
|
|
3308
3189
|
return { success: true, itemId: '${validated.itemId}', message: 'Mask removed' };
|
|
3309
3190
|
} catch (error) {
|
|
3310
3191
|
return { success: false, error: error.message };
|
|
@@ -3398,10 +3279,12 @@ app.historyManager.saveState();
|
|
|
3398
3279
|
})();
|
|
3399
3280
|
`.trim();
|
|
3400
3281
|
}
|
|
3282
|
+
// =============================================================================
|
|
3283
|
+
// TEMPLATE
|
|
3284
|
+
// =============================================================================
|
|
3401
3285
|
generateApplyTemplate(input) {
|
|
3402
3286
|
const { templateId, category, listOnly } = input;
|
|
3403
3287
|
if (listOnly || !templateId) {
|
|
3404
|
-
// List mode: return available templates, optionally filtered by category
|
|
3405
3288
|
const categoryFilter = category ? `'${category}'` : 'null';
|
|
3406
3289
|
return `
|
|
3407
3290
|
// List available templates
|
|
@@ -3427,7 +3310,6 @@ app.historyManager.saveState();
|
|
|
3427
3310
|
})();
|
|
3428
3311
|
`.trim();
|
|
3429
3312
|
}
|
|
3430
|
-
// Load mode: apply a specific template
|
|
3431
3313
|
return `
|
|
3432
3314
|
// Apply template: ${templateId}
|
|
3433
3315
|
(async function() {
|
|
@@ -3703,7 +3585,7 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
3703
3585
|
// Select all items
|
|
3704
3586
|
(function() {
|
|
3705
3587
|
app.selectAll();
|
|
3706
|
-
const items = app.
|
|
3588
|
+
const items = app.getSelectedItems().map(i => i.data?.itemId || i.name || i.id);
|
|
3707
3589
|
return { success: true, action: 'select_all', count: items.length, items };
|
|
3708
3590
|
})();`.trim();
|
|
3709
3591
|
case 'deselect_all':
|
|
@@ -3717,7 +3599,7 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
3717
3599
|
return `
|
|
3718
3600
|
// Get current selection
|
|
3719
3601
|
(function() {
|
|
3720
|
-
const items = app.
|
|
3602
|
+
const items = app.getSelectedItems().map(i => ({
|
|
3721
3603
|
itemId: i.data?.itemId || i.name || i.id,
|
|
3722
3604
|
type: i.data?.itemType || i.className,
|
|
3723
3605
|
bounds: i.bounds ? { x: i.bounds.x, y: i.bounds.y, width: i.bounds.width, height: i.bounds.height } : null
|
|
@@ -3728,7 +3610,7 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
3728
3610
|
return `
|
|
3729
3611
|
// Delete selected items
|
|
3730
3612
|
(function() {
|
|
3731
|
-
const selection = app.
|
|
3613
|
+
const selection = app.getSelectedItems();
|
|
3732
3614
|
const deleted = selection.map(i => i.data?.itemId || i.name || i.id);
|
|
3733
3615
|
app.deleteSelected();
|
|
3734
3616
|
return { success: true, action: 'delete_selected', deleted, count: deleted.length };
|
|
@@ -3962,29 +3844,6 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
3962
3844
|
return `(function() { return { error: 'Unknown precomp action: ${input.action}' }; })();`;
|
|
3963
3845
|
}
|
|
3964
3846
|
}
|
|
3965
|
-
generateView(input) {
|
|
3966
|
-
switch (input.action) {
|
|
3967
|
-
case 'fit': {
|
|
3968
|
-
const mode = input.mode || 'content';
|
|
3969
|
-
const padding = input.padding ?? 20;
|
|
3970
|
-
return `
|
|
3971
|
-
// Fit view to ${mode}
|
|
3972
|
-
(function() {
|
|
3973
|
-
app.fitView('${mode}', ${padding});
|
|
3974
|
-
return { success: true, action: 'fit', mode: '${mode}', padding: ${padding} };
|
|
3975
|
-
})();`.trim();
|
|
3976
|
-
}
|
|
3977
|
-
case 'get_state':
|
|
3978
|
-
return `
|
|
3979
|
-
// Get current view state
|
|
3980
|
-
(function() {
|
|
3981
|
-
const state = app.getViewState();
|
|
3982
|
-
return { success: true, action: 'get_state', ...state };
|
|
3983
|
-
})();`.trim();
|
|
3984
|
-
default:
|
|
3985
|
-
return `(function() { return { error: 'Unknown view action: ${input.action}' }; })();`;
|
|
3986
|
-
}
|
|
3987
|
-
}
|
|
3988
3847
|
generateBackground(input) {
|
|
3989
3848
|
switch (input.action) {
|
|
3990
3849
|
case 'set': {
|
|
@@ -4028,8 +3887,9 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
4028
3887
|
return `
|
|
4029
3888
|
// Get background info
|
|
4030
3889
|
(function() {
|
|
4031
|
-
|
|
4032
|
-
|
|
3890
|
+
// FxTool exposes getBackgroundMode() returning the mode string only.
|
|
3891
|
+
const mode = app.getBackgroundMode ? app.getBackgroundMode() : null;
|
|
3892
|
+
return { success: true, action: 'get', mode };
|
|
4033
3893
|
})();`.trim();
|
|
4034
3894
|
default:
|
|
4035
3895
|
return `(function() { return { error: 'Unknown background action: ${input.action}' }; })();`;
|
|
@@ -4094,6 +3954,541 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
4094
3954
|
return `(function() { return { error: 'Unknown query action: ${input.action}' }; })();`;
|
|
4095
3955
|
}
|
|
4096
3956
|
}
|
|
3957
|
+
// ===========================================================================
|
|
3958
|
+
// DEFORMATION
|
|
3959
|
+
// ===========================================================================
|
|
3960
|
+
generateDeform(input) {
|
|
3961
|
+
const guard = `if (!app.deformPresets) return { error: 'DeformPresets not available' };`;
|
|
3962
|
+
switch (input.action) {
|
|
3963
|
+
case 'apply': {
|
|
3964
|
+
if (!input.itemId)
|
|
3965
|
+
return `(function() { return { error: 'itemId is required for deform apply' }; })();`;
|
|
3966
|
+
if (!input.preset)
|
|
3967
|
+
return `(function() { return { error: 'preset is required for deform apply' }; })();`;
|
|
3968
|
+
const params = {};
|
|
3969
|
+
if (input.frequency !== undefined)
|
|
3970
|
+
params.frequency = input.frequency;
|
|
3971
|
+
if (input.amplitude !== undefined)
|
|
3972
|
+
params.amplitude = input.amplitude;
|
|
3973
|
+
if (input.phase)
|
|
3974
|
+
params.phase = input.phase;
|
|
3975
|
+
if (input.loop !== undefined)
|
|
3976
|
+
params.loop = input.loop;
|
|
3977
|
+
if (input.axis)
|
|
3978
|
+
params.axis = input.axis;
|
|
3979
|
+
if (input.turns !== undefined)
|
|
3980
|
+
params.turns = input.turns;
|
|
3981
|
+
if (input.waves !== undefined)
|
|
3982
|
+
params.waves = input.waves;
|
|
3983
|
+
if (input.maxDisplacement !== undefined)
|
|
3984
|
+
params.maxDisplacement = input.maxDisplacement;
|
|
3985
|
+
if (input.speed !== undefined)
|
|
3986
|
+
params.speed = input.speed;
|
|
3987
|
+
if (input.steps !== undefined)
|
|
3988
|
+
params.steps = input.steps;
|
|
3989
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
3990
|
+
return `
|
|
3991
|
+
// Apply deformation preset
|
|
3992
|
+
(function() {
|
|
3993
|
+
${guard}
|
|
3994
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
3995
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
3996
|
+
app.deformPresets.apply(entry.item, '${input.preset}', ${JSON.stringify(params)});
|
|
3997
|
+
return { success: true, action: 'apply', itemId: ${itemIdStr}, preset: '${input.preset}' };
|
|
3998
|
+
})();`.trim();
|
|
3999
|
+
}
|
|
4000
|
+
case 'trigger': {
|
|
4001
|
+
if (!input.itemId)
|
|
4002
|
+
return `(function() { return { error: 'itemId is required for deform trigger' }; })();`;
|
|
4003
|
+
if (!input.preset)
|
|
4004
|
+
return `(function() { return { error: 'preset is required for deform trigger' }; })();`;
|
|
4005
|
+
const params = {};
|
|
4006
|
+
if (input.amplitude !== undefined)
|
|
4007
|
+
params.amplitude = input.amplitude;
|
|
4008
|
+
if (input.speed !== undefined)
|
|
4009
|
+
params.speed = input.speed;
|
|
4010
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4011
|
+
return `
|
|
4012
|
+
// Trigger one-shot deformation
|
|
4013
|
+
(function() {
|
|
4014
|
+
${guard}
|
|
4015
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4016
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4017
|
+
app.deformPresets.trigger(entry.item, '${input.preset}', ${JSON.stringify(params)});
|
|
4018
|
+
return { success: true, action: 'trigger', itemId: ${itemIdStr}, preset: '${input.preset}' };
|
|
4019
|
+
})();`.trim();
|
|
4020
|
+
}
|
|
4021
|
+
case 'remove': {
|
|
4022
|
+
if (!input.itemId)
|
|
4023
|
+
return `(function() { return { error: 'itemId is required for deform remove' }; })();`;
|
|
4024
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4025
|
+
return `
|
|
4026
|
+
// Remove deformation and restore geometry
|
|
4027
|
+
(function() {
|
|
4028
|
+
${guard}
|
|
4029
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4030
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4031
|
+
app.deformPresets.remove(entry.item);
|
|
4032
|
+
return { success: true, action: 'remove', itemId: ${itemIdStr} };
|
|
4033
|
+
})();`.trim();
|
|
4034
|
+
}
|
|
4035
|
+
default:
|
|
4036
|
+
return `(function() { return { error: 'Unknown deform action: ${input.action}' }; })();`;
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
// ===========================================================================
|
|
4040
|
+
// SPRITE SHEETS
|
|
4041
|
+
// ===========================================================================
|
|
4042
|
+
generateSpriteSheet(input) {
|
|
4043
|
+
const guard = `if (!app.spriteSheetSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
4044
|
+
switch (input.action) {
|
|
4045
|
+
case 'generate': {
|
|
4046
|
+
const opts = {};
|
|
4047
|
+
if (input.poses)
|
|
4048
|
+
opts.poses = input.poses;
|
|
4049
|
+
if (input.transition)
|
|
4050
|
+
opts.transition = input.transition;
|
|
4051
|
+
if (input.bakedAnimation)
|
|
4052
|
+
opts.bakedAnimation = input.bakedAnimation;
|
|
4053
|
+
if (input.animations)
|
|
4054
|
+
opts.animations = input.animations;
|
|
4055
|
+
if (input.padding !== undefined)
|
|
4056
|
+
opts.padding = input.padding;
|
|
4057
|
+
if (input.name)
|
|
4058
|
+
opts.name = input.name;
|
|
4059
|
+
return `
|
|
4060
|
+
// Generate sprite sheet from skeleton
|
|
4061
|
+
(async function() {
|
|
4062
|
+
${guard}
|
|
4063
|
+
const sheet = await app.spriteSheetSystem.generateSpriteSheet(${JSON.stringify(input.skeletonId || '')}, ${JSON.stringify(opts)});
|
|
4064
|
+
return { success: true, action: 'generate', spriteSheetId: sheet.id, name: sheet.name, width: sheet.atlasWidth, height: sheet.atlasHeight, frameCount: sheet.frames?.size ?? sheet.frames?.length ?? 0 };
|
|
4065
|
+
})();`.trim();
|
|
4066
|
+
}
|
|
4067
|
+
case 'play': {
|
|
4068
|
+
const opts = {};
|
|
4069
|
+
if (input.x !== undefined)
|
|
4070
|
+
opts.x = input.x;
|
|
4071
|
+
if (input.y !== undefined)
|
|
4072
|
+
opts.y = input.y;
|
|
4073
|
+
if (input.animation)
|
|
4074
|
+
opts.animation = input.animation;
|
|
4075
|
+
if (input.fps !== undefined)
|
|
4076
|
+
opts.fps = input.fps;
|
|
4077
|
+
if (input.scale !== undefined)
|
|
4078
|
+
opts.scale = input.scale;
|
|
4079
|
+
return `
|
|
4080
|
+
// Play sprite sheet animation
|
|
4081
|
+
(async function() {
|
|
4082
|
+
${guard}
|
|
4083
|
+
const player = await app.spriteSheetSystem.playSpriteSheet(${JSON.stringify(input.spriteSheetId || '')}, ${JSON.stringify(opts)});
|
|
4084
|
+
return { success: true, action: 'play', playerId: player.id };
|
|
4085
|
+
})();`.trim();
|
|
4086
|
+
}
|
|
4087
|
+
case 'export':
|
|
4088
|
+
return `
|
|
4089
|
+
// Export sprite sheet
|
|
4090
|
+
(async function() {
|
|
4091
|
+
${guard}
|
|
4092
|
+
const result = await app.spriteSheetSystem.exportSpriteSheet(${JSON.stringify(input.spriteSheetId || '')}, { format: '${input.format || 'png'}', download: true, includeMetadata: true });
|
|
4093
|
+
return { success: true, action: 'export', format: '${input.format || 'png'}' };
|
|
4094
|
+
})();`.trim();
|
|
4095
|
+
default:
|
|
4096
|
+
return `(function() { return { error: 'Unknown sprite sheet action: ${input.action}' }; })();`;
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
// ===========================================================================
|
|
4100
|
+
// INTERACTION
|
|
4101
|
+
// ===========================================================================
|
|
4102
|
+
generateInteraction(input) {
|
|
4103
|
+
const guard = `if (!app.interactionSystem) return { error: 'InteractionSystem not available' };`;
|
|
4104
|
+
switch (input.action) {
|
|
4105
|
+
case 'add_behavior': {
|
|
4106
|
+
const itemIdStr = JSON.stringify(input.itemId || '');
|
|
4107
|
+
const params = JSON.stringify(input.params || {});
|
|
4108
|
+
return `
|
|
4109
|
+
// Add behavior to item
|
|
4110
|
+
(function() {
|
|
4111
|
+
${guard}
|
|
4112
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4113
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4114
|
+
const behaviorId = app.interactionSystem.addContinuousBehavior(entry.item, '${input.behaviorType}', ${params});
|
|
4115
|
+
return { success: true, action: 'add_behavior', itemId: ${itemIdStr}, behaviorType: '${input.behaviorType}', behaviorId };
|
|
4116
|
+
})();`.trim();
|
|
4117
|
+
}
|
|
4118
|
+
case 'remove_behavior': {
|
|
4119
|
+
const itemIdStr = JSON.stringify(input.itemId || '');
|
|
4120
|
+
const behaviorIdStr = JSON.stringify(input.behaviorId || '');
|
|
4121
|
+
return `
|
|
4122
|
+
// Remove behavior from item
|
|
4123
|
+
(function() {
|
|
4124
|
+
${guard}
|
|
4125
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4126
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4127
|
+
app.interactionSystem.removeContinuousBehavior(entry.item, ${behaviorIdStr});
|
|
4128
|
+
return { success: true, action: 'remove_behavior', itemId: ${itemIdStr}, behaviorId: ${behaviorIdStr} };
|
|
4129
|
+
})();`.trim();
|
|
4130
|
+
}
|
|
4131
|
+
case 'trigger_action': {
|
|
4132
|
+
const params = JSON.stringify(input.params || {});
|
|
4133
|
+
return `
|
|
4134
|
+
// Trigger interaction action
|
|
4135
|
+
(function() {
|
|
4136
|
+
${guard}
|
|
4137
|
+
app.interactionSystem.triggerAction('${input.actionType}', ${params});
|
|
4138
|
+
return { success: true, action: 'trigger_action', actionType: '${input.actionType}' };
|
|
4139
|
+
})();`.trim();
|
|
4140
|
+
}
|
|
4141
|
+
case 'get_state':
|
|
4142
|
+
return `
|
|
4143
|
+
// Get interaction state
|
|
4144
|
+
(function() {
|
|
4145
|
+
${guard}
|
|
4146
|
+
const state = app.interactionSystem.getState ? app.interactionSystem.getState() : {};
|
|
4147
|
+
return { success: true, action: 'get_state', ...state };
|
|
4148
|
+
})();`.trim();
|
|
4149
|
+
default:
|
|
4150
|
+
return `(function() { return { error: 'Unknown interaction action: ${input.action}' }; })();`;
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
// ===========================================================================
|
|
4154
|
+
// WIDGET EXPORT
|
|
4155
|
+
// ===========================================================================
|
|
4156
|
+
generateExportWidget(input) {
|
|
4157
|
+
const opts = {};
|
|
4158
|
+
if (input.download !== undefined)
|
|
4159
|
+
opts.download = input.download;
|
|
4160
|
+
if (input.filename)
|
|
4161
|
+
opts.filename = input.filename;
|
|
4162
|
+
if (input.includeInteractions !== undefined)
|
|
4163
|
+
opts.includeInteractions = input.includeInteractions;
|
|
4164
|
+
if (input.minify !== undefined)
|
|
4165
|
+
opts.minify = input.minify;
|
|
4166
|
+
return `
|
|
4167
|
+
// Export widget (pp:PinePaper ontology JSON)
|
|
4168
|
+
(async function() {
|
|
4169
|
+
if (!app.exportEngine || !app.exportEngine.exportWidget) return { error: 'Widget export not available' };
|
|
4170
|
+
const result = await app.exportEngine.exportWidget(${JSON.stringify(opts)});
|
|
4171
|
+
return { success: true, json: result.json, filename: result.filename, embedCode: result.embedCode, metadata: result.data?.metadata };
|
|
4172
|
+
})();`.trim();
|
|
4173
|
+
}
|
|
4174
|
+
generateExportWidgetHtml(input) {
|
|
4175
|
+
const opts = {};
|
|
4176
|
+
if (input.title)
|
|
4177
|
+
opts.title = input.title;
|
|
4178
|
+
if (input.download !== undefined)
|
|
4179
|
+
opts.download = input.download;
|
|
4180
|
+
return `
|
|
4181
|
+
// Export widget as self-contained HTML
|
|
4182
|
+
(async function() {
|
|
4183
|
+
if (!app.exportEngine || !app.exportEngine.exportWidgetHTML) return { error: 'Widget HTML export not available' };
|
|
4184
|
+
const result = await app.exportEngine.exportWidgetHTML(${JSON.stringify(opts)});
|
|
4185
|
+
return { success: true, html: result.html, estimatedSize: result.estimatedSize, analysis: { itemTypes: [...result.analysis.itemTypes], relationTypes: [...result.analysis.relationTypes], hasSimpleAnimations: result.analysis.hasSimpleAnimations, hasKeyframeAnimations: result.analysis.hasKeyframeAnimations, hasMasks: result.analysis.hasMasks } };
|
|
4186
|
+
})();`.trim();
|
|
4187
|
+
}
|
|
4188
|
+
// ===========================================================================
|
|
4189
|
+
// DATA VISUALIZATION (CHARTS)
|
|
4190
|
+
// ===========================================================================
|
|
4191
|
+
generateChart(input) {
|
|
4192
|
+
const guard = `if (!app.chartSystem) return { error: 'ChartSystem not available' };`;
|
|
4193
|
+
switch (input.action) {
|
|
4194
|
+
case 'create': {
|
|
4195
|
+
if (!input.chartType)
|
|
4196
|
+
return `(function() { return { error: 'chartType is required for create' }; })();`;
|
|
4197
|
+
if (!input.data)
|
|
4198
|
+
return `(function() { return { error: 'data is required for create' }; })();`;
|
|
4199
|
+
const dataStr = JSON.stringify(input.data);
|
|
4200
|
+
const optsStr = JSON.stringify(input.options || {});
|
|
4201
|
+
return `
|
|
4202
|
+
// Create chart
|
|
4203
|
+
(function() {
|
|
4204
|
+
${guard}
|
|
4205
|
+
const group = app.createChart(${JSON.stringify(input.chartType)}, ${dataStr}, ${optsStr});
|
|
4206
|
+
if (!group) return { error: 'Failed to create chart' };
|
|
4207
|
+
const chartId = app.chartSystem.getLastChartId();
|
|
4208
|
+
return { success: true, action: 'create', chartType: ${JSON.stringify(input.chartType)}, chartId, dataPoints: ${input.data.length} };
|
|
4209
|
+
})();`.trim();
|
|
4210
|
+
}
|
|
4211
|
+
case 'update': {
|
|
4212
|
+
if (!input.chartId)
|
|
4213
|
+
return `(function() { return { error: 'chartId is required for update' }; })();`;
|
|
4214
|
+
const chartIdStr = JSON.stringify(input.chartId);
|
|
4215
|
+
const dataStr = JSON.stringify(input.data || []);
|
|
4216
|
+
const optsStr = JSON.stringify(input.options || {});
|
|
4217
|
+
return `
|
|
4218
|
+
// Update chart data
|
|
4219
|
+
(function() {
|
|
4220
|
+
${guard}
|
|
4221
|
+
const chart = app.chartSystem.getChart(${chartIdStr});
|
|
4222
|
+
if (!chart) return { error: 'Chart not found: ' + ${chartIdStr} };
|
|
4223
|
+
app.chartSystem.update(${chartIdStr}, ${dataStr}, ${optsStr});
|
|
4224
|
+
return { success: true, action: 'update', chartId: ${chartIdStr} };
|
|
4225
|
+
})();`.trim();
|
|
4226
|
+
}
|
|
4227
|
+
case 'reconfigure': {
|
|
4228
|
+
if (!input.chartId)
|
|
4229
|
+
return `(function() { return { error: 'chartId is required for reconfigure' }; })();`;
|
|
4230
|
+
const chartIdStr = JSON.stringify(input.chartId);
|
|
4231
|
+
const optsStr = JSON.stringify(input.options || {});
|
|
4232
|
+
return `
|
|
4233
|
+
// Reconfigure chart styling
|
|
4234
|
+
(function() {
|
|
4235
|
+
${guard}
|
|
4236
|
+
const chart = app.chartSystem.getChart(${chartIdStr});
|
|
4237
|
+
if (!chart) return { error: 'Chart not found: ' + ${chartIdStr} };
|
|
4238
|
+
app.chartSystem.reconfigure(${chartIdStr}, ${optsStr});
|
|
4239
|
+
return { success: true, action: 'reconfigure', chartId: ${chartIdStr} };
|
|
4240
|
+
})();`.trim();
|
|
4241
|
+
}
|
|
4242
|
+
case 'remove': {
|
|
4243
|
+
if (!input.chartId)
|
|
4244
|
+
return `(function() { return { error: 'chartId is required for remove' }; })();`;
|
|
4245
|
+
const chartIdStr = JSON.stringify(input.chartId);
|
|
4246
|
+
return `
|
|
4247
|
+
// Remove chart
|
|
4248
|
+
(function() {
|
|
4249
|
+
${guard}
|
|
4250
|
+
app.chartSystem.remove(${chartIdStr});
|
|
4251
|
+
return { success: true, action: 'remove', chartId: ${chartIdStr} };
|
|
4252
|
+
})();`.trim();
|
|
4253
|
+
}
|
|
4254
|
+
default:
|
|
4255
|
+
return `(function() { return { error: 'Unknown chart action: ${input.action}' }; })();`;
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
// ===========================================================================
|
|
4259
|
+
// MAGIC SYSTEM
|
|
4260
|
+
// ===========================================================================
|
|
4261
|
+
generateMagic(input) {
|
|
4262
|
+
const guard = `if (!app.magicSystem) return { error: 'MagicSystem not available' };`;
|
|
4263
|
+
switch (input.action) {
|
|
4264
|
+
case 'animate': {
|
|
4265
|
+
const opts = {};
|
|
4266
|
+
if (input.mood)
|
|
4267
|
+
opts.energy = input.mood;
|
|
4268
|
+
if (input.selectionOnly !== undefined)
|
|
4269
|
+
opts.selectionOnly = input.selectionOnly;
|
|
4270
|
+
return `
|
|
4271
|
+
// Magic auto-animate
|
|
4272
|
+
(async function() {
|
|
4273
|
+
${guard}
|
|
4274
|
+
const result = await app.magicSystem.autoAnimate(${JSON.stringify(opts)});
|
|
4275
|
+
return { success: true, action: 'animate', itemsAnimated: result.count, mood: result.energy, type: result.type };
|
|
4276
|
+
})();`.trim();
|
|
4277
|
+
}
|
|
4278
|
+
case 'remix': {
|
|
4279
|
+
const opts = {};
|
|
4280
|
+
if (input.selectionOnly !== undefined)
|
|
4281
|
+
opts.selectionOnly = input.selectionOnly;
|
|
4282
|
+
return `
|
|
4283
|
+
// Magic style remix
|
|
4284
|
+
(async function() {
|
|
4285
|
+
${guard}
|
|
4286
|
+
const result = await app.magicSystem.remixStyle(${JSON.stringify(opts)});
|
|
4287
|
+
return { success: true, action: 'remix', itemsRemixed: result.count, palette: result.palette };
|
|
4288
|
+
})();`.trim();
|
|
4289
|
+
}
|
|
4290
|
+
default:
|
|
4291
|
+
return `(function() { return { error: 'Unknown magic action: ${input.action}' }; })();`;
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
// ===========================================================================
|
|
4295
|
+
// PHYSICS (RIGID BODY SIMULATION)
|
|
4296
|
+
// ===========================================================================
|
|
4297
|
+
generatePhysics(input) {
|
|
4298
|
+
const guard = `if (!app.physicsWorld) return { error: 'PhysicsWorld not available' };`;
|
|
4299
|
+
switch (input.action) {
|
|
4300
|
+
case 'init': {
|
|
4301
|
+
const gravity = JSON.stringify(input.gravity || { x: 0, y: 980 });
|
|
4302
|
+
return `
|
|
4303
|
+
// Initialize physics world
|
|
4304
|
+
(function() {
|
|
4305
|
+
${guard}
|
|
4306
|
+
app.physicsWorld.init({ gravity: ${gravity} });
|
|
4307
|
+
return { success: true, action: 'init', gravity: ${gravity} };
|
|
4308
|
+
})();`.trim();
|
|
4309
|
+
}
|
|
4310
|
+
case 'add_body': {
|
|
4311
|
+
if (!input.itemId)
|
|
4312
|
+
return `(function() { return { error: 'itemId is required for add_body' }; })();`;
|
|
4313
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4314
|
+
const config = {};
|
|
4315
|
+
if (input.bodyType)
|
|
4316
|
+
config.type = input.bodyType;
|
|
4317
|
+
if (input.mass !== undefined)
|
|
4318
|
+
config.mass = input.mass;
|
|
4319
|
+
if (input.friction !== undefined)
|
|
4320
|
+
config.friction = input.friction;
|
|
4321
|
+
if (input.restitution !== undefined)
|
|
4322
|
+
config.restitution = input.restitution;
|
|
4323
|
+
if (input.fixedRotation !== undefined)
|
|
4324
|
+
config.fixedRotation = input.fixedRotation;
|
|
4325
|
+
if (input.shape)
|
|
4326
|
+
config.shape = input.shape;
|
|
4327
|
+
return `
|
|
4328
|
+
// Add physics body
|
|
4329
|
+
(function() {
|
|
4330
|
+
${guard}
|
|
4331
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4332
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4333
|
+
const ok = app.physicsWorld.addBody(${itemIdStr}, ${JSON.stringify(config)});
|
|
4334
|
+
return { success: ok, action: 'add_body', itemId: ${itemIdStr}, bodyType: ${JSON.stringify(input.bodyType || 'dynamic')} };
|
|
4335
|
+
})();`.trim();
|
|
4336
|
+
}
|
|
4337
|
+
case 'remove_body': {
|
|
4338
|
+
if (!input.itemId)
|
|
4339
|
+
return `(function() { return { error: 'itemId is required for remove_body' }; })();`;
|
|
4340
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4341
|
+
return `
|
|
4342
|
+
// Remove physics body
|
|
4343
|
+
(function() {
|
|
4344
|
+
${guard}
|
|
4345
|
+
app.physicsWorld.removeBody(${itemIdStr});
|
|
4346
|
+
return { success: true, action: 'remove_body', itemId: ${itemIdStr} };
|
|
4347
|
+
})();`.trim();
|
|
4348
|
+
}
|
|
4349
|
+
case 'apply_force': {
|
|
4350
|
+
if (!input.itemId)
|
|
4351
|
+
return `(function() { return { error: 'itemId is required for apply_force' }; })();`;
|
|
4352
|
+
if (!input.force)
|
|
4353
|
+
return `(function() { return { error: 'force is required for apply_force' }; })();`;
|
|
4354
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4355
|
+
return `
|
|
4356
|
+
// Apply continuous force
|
|
4357
|
+
(function() {
|
|
4358
|
+
${guard}
|
|
4359
|
+
app.physicsWorld.applyForce(${itemIdStr}, ${JSON.stringify(input.force)});
|
|
4360
|
+
return { success: true, action: 'apply_force', itemId: ${itemIdStr}, force: ${JSON.stringify(input.force)} };
|
|
4361
|
+
})();`.trim();
|
|
4362
|
+
}
|
|
4363
|
+
case 'apply_impulse': {
|
|
4364
|
+
if (!input.itemId)
|
|
4365
|
+
return `(function() { return { error: 'itemId is required for apply_impulse' }; })();`;
|
|
4366
|
+
if (!input.impulse)
|
|
4367
|
+
return `(function() { return { error: 'impulse is required for apply_impulse' }; })();`;
|
|
4368
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4369
|
+
return `
|
|
4370
|
+
// Apply instant impulse
|
|
4371
|
+
(function() {
|
|
4372
|
+
${guard}
|
|
4373
|
+
app.physicsWorld.applyImpulse(${itemIdStr}, ${JSON.stringify(input.impulse)});
|
|
4374
|
+
return { success: true, action: 'apply_impulse', itemId: ${itemIdStr}, impulse: ${JSON.stringify(input.impulse)} };
|
|
4375
|
+
})();`.trim();
|
|
4376
|
+
}
|
|
4377
|
+
case 'set_velocity': {
|
|
4378
|
+
if (!input.itemId)
|
|
4379
|
+
return `(function() { return { error: 'itemId is required for set_velocity' }; })();`;
|
|
4380
|
+
if (!input.velocity)
|
|
4381
|
+
return `(function() { return { error: 'velocity is required for set_velocity' }; })();`;
|
|
4382
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4383
|
+
return `
|
|
4384
|
+
// Set body velocity
|
|
4385
|
+
(function() {
|
|
4386
|
+
${guard}
|
|
4387
|
+
app.physicsWorld.setVelocity(${itemIdStr}, ${JSON.stringify(input.velocity)});
|
|
4388
|
+
return { success: true, action: 'set_velocity', itemId: ${itemIdStr}, velocity: ${JSON.stringify(input.velocity)} };
|
|
4389
|
+
})();`.trim();
|
|
4390
|
+
}
|
|
4391
|
+
case 'get_state': {
|
|
4392
|
+
if (!input.itemId)
|
|
4393
|
+
return `(function() { return { error: 'itemId is required for get_state' }; })();`;
|
|
4394
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4395
|
+
return `
|
|
4396
|
+
// Get physics body state
|
|
4397
|
+
(function() {
|
|
4398
|
+
${guard}
|
|
4399
|
+
const state = app.physicsWorld.getBodyState(${itemIdStr});
|
|
4400
|
+
if (!state) return { error: 'No physics body for item: ' + ${itemIdStr} };
|
|
4401
|
+
return { success: true, action: 'get_state', itemId: ${itemIdStr}, ...state };
|
|
4402
|
+
})();`.trim();
|
|
4403
|
+
}
|
|
4404
|
+
case 'create_ground': {
|
|
4405
|
+
const y = input.y !== undefined ? input.y : 500;
|
|
4406
|
+
const w = input.width !== undefined ? input.width : 2000;
|
|
4407
|
+
return `
|
|
4408
|
+
// Create ground plane
|
|
4409
|
+
(function() {
|
|
4410
|
+
${guard}
|
|
4411
|
+
const groundId = app.physicsWorld.createGround(${y}, ${w});
|
|
4412
|
+
return { success: true, action: 'create_ground', groundId, y: ${y}, width: ${w} };
|
|
4413
|
+
})();`.trim();
|
|
4414
|
+
}
|
|
4415
|
+
case 'create_joint': {
|
|
4416
|
+
if (!input.itemId)
|
|
4417
|
+
return `(function() { return { error: 'itemId is required for create_joint' }; })();`;
|
|
4418
|
+
if (!input.targetItemId)
|
|
4419
|
+
return `(function() { return { error: 'targetItemId is required for create_joint' }; })();`;
|
|
4420
|
+
if (!input.jointType)
|
|
4421
|
+
return `(function() { return { error: 'jointType is required for create_joint' }; })();`;
|
|
4422
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4423
|
+
const targetStr = JSON.stringify(input.targetItemId);
|
|
4424
|
+
const params = JSON.stringify({ type: input.jointType, ...(input.jointParams || {}) });
|
|
4425
|
+
return `
|
|
4426
|
+
// Create physics joint
|
|
4427
|
+
(function() {
|
|
4428
|
+
${guard}
|
|
4429
|
+
const joint = app.physicsWorld.createJoint(${itemIdStr}, ${targetStr}, ${params});
|
|
4430
|
+
if (!joint) return { error: 'Failed to create joint between ' + ${itemIdStr} + ' and ' + ${targetStr} };
|
|
4431
|
+
return { success: true, action: 'create_joint', itemId: ${itemIdStr}, targetItemId: ${targetStr}, jointType: ${JSON.stringify(input.jointType)} };
|
|
4432
|
+
})();`.trim();
|
|
4433
|
+
}
|
|
4434
|
+
default:
|
|
4435
|
+
return `(function() { return { error: 'Unknown physics action: ${input.action}' }; })();`;
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
// ===========================================================================
|
|
4439
|
+
// MEASUREMENT SYSTEM
|
|
4440
|
+
// ===========================================================================
|
|
4441
|
+
generateMeasurement(input) {
|
|
4442
|
+
const guard = `if (!app.measurementSystem) return { error: 'MeasurementSystem not available' };`;
|
|
4443
|
+
switch (input.action) {
|
|
4444
|
+
case 'set_rulers': {
|
|
4445
|
+
const enabled = input.enabled !== false;
|
|
4446
|
+
return `
|
|
4447
|
+
// Toggle rulers
|
|
4448
|
+
(function() {
|
|
4449
|
+
${guard}
|
|
4450
|
+
app.measurementSystem.setRulersVisible(${enabled});
|
|
4451
|
+
return { success: true, action: 'set_rulers', enabled: ${enabled} };
|
|
4452
|
+
})();`.trim();
|
|
4453
|
+
}
|
|
4454
|
+
case 'set_grid': {
|
|
4455
|
+
const enabled = input.enabled !== false;
|
|
4456
|
+
return `
|
|
4457
|
+
// Toggle grid
|
|
4458
|
+
(function() {
|
|
4459
|
+
${guard}
|
|
4460
|
+
app.measurementSystem.setGridVisible(${enabled});
|
|
4461
|
+
return { success: true, action: 'set_grid', enabled: ${enabled} };
|
|
4462
|
+
})();`.trim();
|
|
4463
|
+
}
|
|
4464
|
+
// No measurementSystem guard — reads from itemRegistry directly
|
|
4465
|
+
case 'get_dimensions': {
|
|
4466
|
+
if (!input.itemId)
|
|
4467
|
+
return `(function() { return { error: 'itemId is required for get_dimensions' }; })();`;
|
|
4468
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4469
|
+
return `
|
|
4470
|
+
// Get item dimensions
|
|
4471
|
+
(function() {
|
|
4472
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4473
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4474
|
+
const b = entry.item.bounds;
|
|
4475
|
+
return { success: true, action: 'get_dimensions', itemId: ${itemIdStr}, x: b.x, y: b.y, width: b.width, height: b.height, rotation: entry.item.rotation || 0 };
|
|
4476
|
+
})();`.trim();
|
|
4477
|
+
}
|
|
4478
|
+
case 'set_snap': {
|
|
4479
|
+
const enabled = input.enabled !== false;
|
|
4480
|
+
return `
|
|
4481
|
+
// Toggle snap to grid
|
|
4482
|
+
(function() {
|
|
4483
|
+
${guard}
|
|
4484
|
+
app.measurementSystem.setSnapToUnitEnabled(${enabled});
|
|
4485
|
+
return { success: true, action: 'set_snap', enabled: ${enabled} };
|
|
4486
|
+
})();`.trim();
|
|
4487
|
+
}
|
|
4488
|
+
default:
|
|
4489
|
+
return `(function() { return { error: 'Unknown measurement action: ${input.action}' }; })();`;
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4097
4492
|
}
|
|
4098
4493
|
// Export singleton instance
|
|
4099
4494
|
export const codeGenerator = new PinePaperCodeGenerator();
|