@pinepaper.studio/mcp-server 1.5.3 → 1.5.4
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/index.d.ts.map +1 -1
- package/dist/index.js +47 -2
- 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 -8
- 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 +1090 -1639
- 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 +94 -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 +205 -603
- 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 -54
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +36 -45
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +36 -1
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +823 -35
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +1116 -444
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +290 -18
- package/dist/types/schemas.js.map +1 -1
- package/package.json +1 -1
|
@@ -82,7 +82,7 @@ function generateBlendModeCode(itemVarName, blendMode) {
|
|
|
82
82
|
*/
|
|
83
83
|
function generateCreateItemCode(itemType, position, properties) {
|
|
84
84
|
// Extract special properties that need separate handling
|
|
85
|
-
const { color, fillColor, strokeColor, shadowColor, shadowBlur, shadowOffset, blendMode, opacity, ...baseProperties } = properties;
|
|
85
|
+
const { color, fillColor, strokeColor, shadowColor, shadowBlur, shadowOffset, blendMode, opacity, contentType, contentFormat, countdownTarget, countdownEndText, ...baseProperties } = properties;
|
|
86
86
|
const params = {
|
|
87
87
|
x: position.x,
|
|
88
88
|
y: position.y,
|
|
@@ -127,6 +127,17 @@ const item = app.create('${itemType}', ${JSON.stringify(params, null, 2)});`;
|
|
|
127
127
|
}
|
|
128
128
|
// Ensure item is visible above backgrounds/generators
|
|
129
129
|
code += `\nif (item.bringToFront) item.bringToFront();`;
|
|
130
|
+
// Add dynamic content support for text items
|
|
131
|
+
if (contentType && itemType === 'text') {
|
|
132
|
+
const opts = {};
|
|
133
|
+
if (contentFormat)
|
|
134
|
+
opts.format = contentFormat;
|
|
135
|
+
if (countdownTarget !== undefined)
|
|
136
|
+
opts.countdownTarget = countdownTarget;
|
|
137
|
+
if (countdownEndText)
|
|
138
|
+
opts.countdownEndText = countdownEndText;
|
|
139
|
+
code += `\nif (app.setDynamicContent) app.setDynamicContent(item, '${contentType}', ${JSON.stringify(opts)});`;
|
|
140
|
+
}
|
|
130
141
|
code += `
|
|
131
142
|
const itemId = item.data.registryId;
|
|
132
143
|
app.historyManager.saveState();
|
|
@@ -139,15 +150,42 @@ app.historyManager.saveState();
|
|
|
139
150
|
* Template for modifying items
|
|
140
151
|
*/
|
|
141
152
|
function generateModifyItemCode(itemId, properties) {
|
|
142
|
-
|
|
153
|
+
const { contentType, contentFormat, countdownTarget, countdownEndText, ...restProperties } = properties;
|
|
154
|
+
let code = `
|
|
143
155
|
// Modify item ${itemId}
|
|
144
|
-
app.select('${itemId}')
|
|
145
|
-
app.modify
|
|
156
|
+
app.select('${itemId}');`;
|
|
157
|
+
// Pass non-dynamic-content properties to app.modify
|
|
158
|
+
if (Object.keys(restProperties).length > 0) {
|
|
159
|
+
code += `\napp.modify(${JSON.stringify(restProperties, null, 2)});`;
|
|
160
|
+
}
|
|
161
|
+
// Handle dynamic content type changes
|
|
162
|
+
if (contentType !== undefined) {
|
|
163
|
+
if (contentType === null || contentType === 'none' || contentType === '') {
|
|
164
|
+
// Remove dynamic content
|
|
165
|
+
code += `
|
|
166
|
+
const entry = app.itemRegistry.get('${itemId}');
|
|
167
|
+
if (entry && entry.item && app.removeDynamicContent) app.removeDynamicContent(entry.item);`;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Set dynamic content
|
|
171
|
+
const opts = {};
|
|
172
|
+
if (contentFormat)
|
|
173
|
+
opts.format = contentFormat;
|
|
174
|
+
if (countdownTarget !== undefined)
|
|
175
|
+
opts.countdownTarget = countdownTarget;
|
|
176
|
+
if (countdownEndText)
|
|
177
|
+
opts.countdownEndText = countdownEndText;
|
|
178
|
+
code += `
|
|
179
|
+
const entry = app.itemRegistry.get('${itemId}');
|
|
180
|
+
if (entry && entry.item && app.setDynamicContent) app.setDynamicContent(entry.item, '${contentType}', ${JSON.stringify(opts)});`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
code += `
|
|
146
184
|
app.historyManager.saveState();
|
|
147
185
|
|
|
148
186
|
// Return success
|
|
149
|
-
({ success: true, itemId: '${itemId}' })
|
|
150
|
-
|
|
187
|
+
({ success: true, itemId: '${itemId}' });`;
|
|
188
|
+
return code.trim();
|
|
151
189
|
}
|
|
152
190
|
/**
|
|
153
191
|
* Template for deleting items
|
|
@@ -257,14 +295,21 @@ app.animate(item, {
|
|
|
257
295
|
/**
|
|
258
296
|
* Template for keyframe animation
|
|
259
297
|
*/
|
|
260
|
-
function generateKeyframeAnimateCode(itemId, keyframes, duration, loop = false) {
|
|
298
|
+
function generateKeyframeAnimateCode(itemId, keyframes, duration, loop = false, timeOffset, clipInPoint, clipOutPoint) {
|
|
261
299
|
const keyframesJson = JSON.stringify(keyframes, null, 2);
|
|
262
300
|
const calculatedDuration = duration || Math.max(...keyframes.map(k => k.time));
|
|
301
|
+
const opts = { duration: calculatedDuration, loop };
|
|
302
|
+
if (timeOffset !== undefined)
|
|
303
|
+
opts.timeOffset = timeOffset;
|
|
304
|
+
if (clipInPoint !== undefined)
|
|
305
|
+
opts.clipInPoint = clipInPoint;
|
|
306
|
+
if (clipOutPoint !== undefined)
|
|
307
|
+
opts.clipOutPoint = clipOutPoint;
|
|
263
308
|
return `
|
|
264
309
|
// Apply keyframe animation to ${itemId}
|
|
265
|
-
app.addAnimation('${itemId}', ${keyframesJson},
|
|
310
|
+
app.addAnimation('${itemId}', ${keyframesJson}, ${JSON.stringify(opts)});
|
|
266
311
|
|
|
267
|
-
({ success: true, itemId: '${itemId}', duration: ${calculatedDuration}, loop: ${loop} });
|
|
312
|
+
({ success: true, itemId: '${itemId}', duration: ${calculatedDuration}, loop: ${loop}${timeOffset !== undefined ? `, timeOffset: ${timeOffset}` : ''}${clipInPoint !== undefined ? `, clipInPoint: ${clipInPoint}` : ''}${clipOutPoint !== undefined ? `, clipOutPoint: ${clipOutPoint}` : ''} });
|
|
268
313
|
`.trim();
|
|
269
314
|
}
|
|
270
315
|
/**
|
|
@@ -582,7 +627,14 @@ export class PinePaperCodeGenerator {
|
|
|
582
627
|
*/
|
|
583
628
|
generateCreateItem(input) {
|
|
584
629
|
const validated = CreateItemInputSchema.parse(input);
|
|
585
|
-
|
|
630
|
+
const properties = { ...validated.properties };
|
|
631
|
+
if (validated.animationType !== undefined)
|
|
632
|
+
properties.animationType = validated.animationType;
|
|
633
|
+
if (validated.animationSpeed !== undefined)
|
|
634
|
+
properties.animationSpeed = validated.animationSpeed;
|
|
635
|
+
if (validated.keyframes !== undefined)
|
|
636
|
+
properties.keyframes = validated.keyframes;
|
|
637
|
+
return generateCreateItemCode(validated.itemType, validated.position, properties);
|
|
586
638
|
}
|
|
587
639
|
/**
|
|
588
640
|
* Generate code for modifying an item
|
|
@@ -628,7 +680,7 @@ export class PinePaperCodeGenerator {
|
|
|
628
680
|
*/
|
|
629
681
|
generateKeyframeAnimate(input) {
|
|
630
682
|
const validated = KeyframeAnimateInputSchema.parse(input);
|
|
631
|
-
return generateKeyframeAnimateCode(validated.itemId, validated.keyframes, validated.duration, validated.loop);
|
|
683
|
+
return generateKeyframeAnimateCode(validated.itemId, validated.keyframes, validated.duration, validated.loop, validated.timeOffset, validated.clipInPoint, validated.clipOutPoint);
|
|
632
684
|
}
|
|
633
685
|
/**
|
|
634
686
|
* Generate code for executing a generator
|
|
@@ -748,6 +800,70 @@ if (imported) {
|
|
|
748
800
|
return `
|
|
749
801
|
// Error: No SVG source provided
|
|
750
802
|
throw new Error('Either svgString or url must be provided');
|
|
803
|
+
`.trim();
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Generate code for importing a Mermaid diagram (flowchart, stateDiagram,
|
|
807
|
+
* sequenceDiagram, erDiagram, classDiagram).
|
|
808
|
+
*/
|
|
809
|
+
generateImportMermaid(mermaidText, options = {}) {
|
|
810
|
+
const escaped = mermaidText.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
811
|
+
const optsJson = JSON.stringify(options);
|
|
812
|
+
return `
|
|
813
|
+
// Import Mermaid diagram
|
|
814
|
+
(function() {
|
|
815
|
+
if (!app.importMermaid) return { success: false, error: 'importMermaid not available — diagramSystem missing' };
|
|
816
|
+
const mermaidText = \`${escaped}\`;
|
|
817
|
+
const result = app.importMermaid(mermaidText, ${optsJson});
|
|
818
|
+
return {
|
|
819
|
+
success: !!result.success,
|
|
820
|
+
nodeCount: result.nodes ? result.nodes.length : 0,
|
|
821
|
+
edgeCount: result.edges ? result.edges.length : 0,
|
|
822
|
+
nodes: result.nodes || [],
|
|
823
|
+
edges: result.edges || [],
|
|
824
|
+
errors: result.errors || [],
|
|
825
|
+
};
|
|
826
|
+
})();
|
|
827
|
+
`.trim();
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Generate code for capturing the canvas as pp: ontology. Wraps FxTool's
|
|
831
|
+
* app.exportCanvasOntology() — defined in PinePaper.js after the
|
|
832
|
+
* canvas-state-to-ontology promotion. Output mirrors the cloud-sync
|
|
833
|
+
* postMessage contract: { canvasWidth, canvasHeight, itemCount,
|
|
834
|
+
* itemTypes, hasAnimations, relationCount, items, triples, [viewport] }.
|
|
835
|
+
*
|
|
836
|
+
* Failure modes: app missing the method → return { success: false, error }
|
|
837
|
+
* with a clear FxTool-version hint. Internal exceptions propagate from
|
|
838
|
+
* the browser-side function (TripleLanguage warns to console; we don't
|
|
839
|
+
* fail the tool call on triple-only failures).
|
|
840
|
+
*/
|
|
841
|
+
generateGetCanvasOntology(options = {}) {
|
|
842
|
+
const optsJson = JSON.stringify(options);
|
|
843
|
+
return `
|
|
844
|
+
// Capture canvas as pp: ontology
|
|
845
|
+
(function() {
|
|
846
|
+
if (!app.exportCanvasOntology) {
|
|
847
|
+
return {
|
|
848
|
+
success: false,
|
|
849
|
+
error: 'app.exportCanvasOntology not available — requires FxTool with canvas-ontology promotion. Falling back to pinepaper_get_items is acceptable but loses the triples representation.',
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
const ctx = app.exportCanvasOntology(${optsJson});
|
|
853
|
+
return {
|
|
854
|
+
success: true,
|
|
855
|
+
canvasWidth: ctx.canvasWidth,
|
|
856
|
+
canvasHeight: ctx.canvasHeight,
|
|
857
|
+
canvasPreset: ctx.canvasPreset,
|
|
858
|
+
itemCount: ctx.itemCount,
|
|
859
|
+
itemTypes: ctx.itemTypes,
|
|
860
|
+
hasAnimations: ctx.hasAnimations,
|
|
861
|
+
relationCount: ctx.relationCount,
|
|
862
|
+
items: ctx.items,
|
|
863
|
+
triples: ctx.triples,
|
|
864
|
+
viewport: ctx.viewport,
|
|
865
|
+
};
|
|
866
|
+
})();
|
|
751
867
|
`.trim();
|
|
752
868
|
}
|
|
753
869
|
/**
|
|
@@ -1346,10 +1462,11 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
1346
1462
|
*/
|
|
1347
1463
|
generateAgentStartJob(input) {
|
|
1348
1464
|
const validated = AgentStartJobInputSchema.parse(input);
|
|
1349
|
-
const { name, screenshotPolicy, canvasPreset, clearCanvas } = validated;
|
|
1465
|
+
const { name, screenshotPolicy, canvasPreset, clearCanvas, includeOntology } = validated;
|
|
1350
1466
|
const nameStr = name ? `'${name.replace(/'/g, "\\'")}'` : 'null';
|
|
1351
1467
|
const policyStr = screenshotPolicy || 'on_complete';
|
|
1352
1468
|
const shouldClear = clearCanvas !== false;
|
|
1469
|
+
const wantOntology = includeOntology !== false;
|
|
1353
1470
|
let code = `
|
|
1354
1471
|
// Start agent job
|
|
1355
1472
|
(async function() {
|
|
@@ -1378,10 +1495,31 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
1378
1495
|
app.setCanvasSize('${canvasPreset}');
|
|
1379
1496
|
// Allow canvas resize to take effect
|
|
1380
1497
|
await new Promise(r => setTimeout(r, 50));
|
|
1498
|
+
`;
|
|
1499
|
+
}
|
|
1500
|
+
if (wantOntology) {
|
|
1501
|
+
code += `
|
|
1502
|
+
// Capture initial canvas ontology so the agent has baseline context
|
|
1503
|
+
// without a follow-up pinepaper_get_canvas_ontology call. After
|
|
1504
|
+
// clearCanvas this is a tiny empty-state payload; with clearCanvas:false
|
|
1505
|
+
// it captures whatever's already on the canvas. Older FxTool builds
|
|
1506
|
+
// without exportCanvasOntology produce an _ontologyUnavailable: true
|
|
1507
|
+
// marker so the agent knows to fall back to pinepaper_get_items.
|
|
1508
|
+
let _ontology = null;
|
|
1509
|
+
let _ontologyUnavailable = false;
|
|
1510
|
+
try {
|
|
1511
|
+
if (app.exportCanvasOntology) {
|
|
1512
|
+
_ontology = app.exportCanvasOntology();
|
|
1513
|
+
} else {
|
|
1514
|
+
_ontologyUnavailable = true;
|
|
1515
|
+
}
|
|
1516
|
+
} catch (e) {
|
|
1517
|
+
_ontologyUnavailable = true;
|
|
1518
|
+
}
|
|
1381
1519
|
`;
|
|
1382
1520
|
}
|
|
1383
1521
|
code += `
|
|
1384
|
-
// Return job context with canvas size
|
|
1522
|
+
// Return job context with canvas size${wantOntology ? ' + initial ontology' : ''}
|
|
1385
1523
|
const _cs = app.getCanvasSize ? app.getCanvasSize() : { width: 800, height: 600 };
|
|
1386
1524
|
return {
|
|
1387
1525
|
success: true,
|
|
@@ -1390,7 +1528,9 @@ throw new Error('Unknown diagram mode action: ${action}');
|
|
|
1390
1528
|
screenshotPolicy: '${policyStr}',
|
|
1391
1529
|
canvasPreset: ${canvasPreset ? `'${canvasPreset}'` : 'null'},
|
|
1392
1530
|
canvasCleared: ${shouldClear},
|
|
1393
|
-
canvasSize: { width: _cs.width || 800, height: _cs.height || 600 }
|
|
1531
|
+
canvasSize: { width: _cs.width || 800, height: _cs.height || 600 }${wantOntology ? `,
|
|
1532
|
+
ontology: _ontology,
|
|
1533
|
+
ontologyUnavailable: _ontologyUnavailable || undefined` : ''}
|
|
1394
1534
|
};
|
|
1395
1535
|
})();
|
|
1396
1536
|
`;
|
|
@@ -1800,7 +1940,7 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
1800
1940
|
*/
|
|
1801
1941
|
generateAgentExport(input) {
|
|
1802
1942
|
const validated = AgentExportInputSchema.parse(input);
|
|
1803
|
-
const { platform, format, quality } = validated;
|
|
1943
|
+
const { platform, format, quality, framing } = validated;
|
|
1804
1944
|
const qualityLevel = quality || 'standard';
|
|
1805
1945
|
// Quality settings (bitrate in bps for VideoEncoder)
|
|
1806
1946
|
const qualitySettings = {
|
|
@@ -1830,10 +1970,38 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
1830
1970
|
const platform = '${platform}';
|
|
1831
1971
|
const format = '${exportFormat}';
|
|
1832
1972
|
const quality = '${qualityLevel}';
|
|
1973
|
+
const framing = '${framing}';
|
|
1833
1974
|
const settings = ${JSON.stringify(qualitySettings)};
|
|
1834
1975
|
const dimensions = ${JSON.stringify({ width: preset.width, height: preset.height })};
|
|
1835
1976
|
|
|
1836
|
-
|
|
1977
|
+
// Resolve camera-view framing dims when requested. Reads the first
|
|
1978
|
+
// camera_animates keyframe's zoom and divides the canvas dims by it
|
|
1979
|
+
// (mirrors FxTool ExportEngine.js:466-485). Camera animation still
|
|
1980
|
+
// drives motion during export — framing only fixes the output frame.
|
|
1981
|
+
let cameraDims = null;
|
|
1982
|
+
if (framing === 'camera') {
|
|
1983
|
+
if (!['gif', 'mp4', 'webm'].includes(format)) {
|
|
1984
|
+
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.' };
|
|
1985
|
+
}
|
|
1986
|
+
const rr = app.relationRegistry;
|
|
1987
|
+
const hasWalkthrough = rr && typeof rr.hasCameraAnimation === 'function' && rr.hasCameraAnimation();
|
|
1988
|
+
if (!hasWalkthrough) {
|
|
1989
|
+
return { success: false, platform, format, framing, error: 'framing: "camera" requires a camera_animates walkthrough on the canvas. Add one or omit framing.' };
|
|
1990
|
+
}
|
|
1991
|
+
const params = rr.getCameraAnimationParams ? rr.getCameraAnimationParams() : null;
|
|
1992
|
+
if (!params || (params.mode || 'keyframes') !== 'keyframes' || !params.keyframes || !params.keyframes.length) {
|
|
1993
|
+
return { success: false, platform, format, framing, error: 'framing: "camera" requires a keyframe-mode camera walkthrough with at least one keyframe.' };
|
|
1994
|
+
}
|
|
1995
|
+
const firstKf = params.keyframes[0];
|
|
1996
|
+
const firstZoom = firstKf && firstKf.zoom > 0 ? firstKf.zoom : 1;
|
|
1997
|
+
const canvasEl = document.querySelector('canvas');
|
|
1998
|
+
cameraDims = {
|
|
1999
|
+
width: Math.round((canvasEl ? canvasEl.width : dimensions.width) / firstZoom),
|
|
2000
|
+
height: Math.round((canvasEl ? canvasEl.height : dimensions.height) / firstZoom),
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
let result = { success: false, platform, format, quality, framing };
|
|
1837
2005
|
|
|
1838
2006
|
try {
|
|
1839
2007
|
switch (format) {
|
|
@@ -1881,28 +2049,44 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
1881
2049
|
case 'mp4':
|
|
1882
2050
|
case 'webm': {
|
|
1883
2051
|
const videoMimeType = { mp4: 'video/mp4', webm: 'video/webm', gif: 'image/gif' }[format];
|
|
1884
|
-
//
|
|
1885
|
-
|
|
1886
|
-
|
|
2052
|
+
// FxTool VideoExporter._calculateBitrate multiplies a per-resolution
|
|
2053
|
+
// base by settings.quality (0.5-1.0). Our existing per-tier compression
|
|
2054
|
+
// values (0.6 / 0.85 / 0.95) map directly to that scale. Without this,
|
|
2055
|
+
// ExportEngine._quickExportVideo overrides quality with undefined for
|
|
2056
|
+
// mp4/webm, _calculateBitrate yields NaN, and VideoEncoder.configure
|
|
2057
|
+
// rejects with 'malformed bitrate value'.
|
|
2058
|
+
const baseVideoSettings = { format, fps: settings.fps, quality: settings.compression, duration: 5 };
|
|
2059
|
+
const blobToDataUrl = (b) => new Promise(resolve => {
|
|
2060
|
+
const r = new FileReader();
|
|
2061
|
+
r.onloadend = () => resolve(r.result);
|
|
2062
|
+
r.readAsDataURL(b);
|
|
2063
|
+
});
|
|
2064
|
+
// Camera framing requires going direct to videoExporter so width/height
|
|
2065
|
+
// pass through — _quickExportVideo strips dim fields.
|
|
2066
|
+
if (cameraDims && app.exportEngine && app.exportEngine.videoExporter) {
|
|
2067
|
+
const blob = await app.exportEngine.videoExporter.export({ ...baseVideoSettings, width: cameraDims.width, height: cameraDims.height });
|
|
2068
|
+
const dataUrl = await blobToDataUrl(blob);
|
|
2069
|
+
result = { success: true, platform, format, framing, data: dataUrl, mimeType: videoMimeType, size: blob.size, dimensions: cameraDims };
|
|
2070
|
+
} else if ((format === 'mp4' || format === 'webm') && app.exportEngine && app.exportEngine.videoExporter) {
|
|
2071
|
+
// Bypass _quickExportVideo for mp4/webm — it hardcodes
|
|
2072
|
+
// quality:undefined for these formats and produces a NaN bitrate.
|
|
2073
|
+
const blob = await app.exportEngine.videoExporter.export(baseVideoSettings);
|
|
2074
|
+
const dataUrl = await blobToDataUrl(blob);
|
|
2075
|
+
result = { success: true, platform, format, framing, data: dataUrl, mimeType: videoMimeType, size: blob.size };
|
|
2076
|
+
} else if (app.exportEngine && app.exportEngine._quickExportVideo) {
|
|
2077
|
+
// GIF path (working): _quickExportVideo forwards gifQuality to
|
|
2078
|
+
// gif.js. Also serves as the fallback if videoExporter is absent.
|
|
2079
|
+
const videoResult = await app.exportEngine._quickExportVideo(format, baseVideoSettings, false);
|
|
1887
2080
|
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 };
|
|
2081
|
+
const dataUrl = await blobToDataUrl(videoResult.blob);
|
|
2082
|
+
result = { success: true, platform, format, framing, data: dataUrl, mimeType: videoMimeType, size: videoResult.blob.size };
|
|
1894
2083
|
} else {
|
|
1895
2084
|
result = { success: false, error: format.toUpperCase() + ' export returned no data' };
|
|
1896
2085
|
}
|
|
1897
2086
|
} 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 };
|
|
2087
|
+
const blob = await app.exportEngine.videoExporter.export(baseVideoSettings);
|
|
2088
|
+
const dataUrl = await blobToDataUrl(blob);
|
|
2089
|
+
result = { success: true, platform, format, framing, data: dataUrl, mimeType: videoMimeType, size: blob.size };
|
|
1906
2090
|
} else {
|
|
1907
2091
|
result = { success: false, error: format.toUpperCase() + ' export not available' };
|
|
1908
2092
|
}
|
|
@@ -2345,7 +2529,7 @@ app.historyManager.saveState();
|
|
|
2345
2529
|
if (gradientDirection)
|
|
2346
2530
|
options.gradientDirection = gradientDirection;
|
|
2347
2531
|
return `
|
|
2348
|
-
// Create letter collage
|
|
2532
|
+
// Create letter collage
|
|
2349
2533
|
(function() {
|
|
2350
2534
|
const text = ${JSON.stringify(text)};
|
|
2351
2535
|
const options = ${JSON.stringify(options, null, 2)};
|
|
@@ -2360,7 +2544,21 @@ app.historyManager.saveState();
|
|
|
2360
2544
|
app.historyManager.saveState('create_letter_collage');
|
|
2361
2545
|
}
|
|
2362
2546
|
|
|
2363
|
-
|
|
2547
|
+
// Return a SERIALIZABLE summary — never the raw result. result.group and
|
|
2548
|
+
// result.letters are live Paper.js objects (circular parent/children refs,
|
|
2549
|
+
// methods, canvas handles); the browser round-trip (page.evaluate / CDP
|
|
2550
|
+
// return-by-value) can't serialize them, so returning result throws
|
|
2551
|
+
// "Failed to execute code in browser" even though the collage was already
|
|
2552
|
+
// created — a false negative. Surface registry ids instead.
|
|
2553
|
+
const collageId = (result && (result.collageId
|
|
2554
|
+
|| (result.group && result.group.data && (result.group.data.id || result.group.data.registryId)))) || null;
|
|
2555
|
+
const letterIds = (result && Array.isArray(result.letters))
|
|
2556
|
+
? result.letters.map(function(l){ return (l && l.data && (l.data.id || l.data.registryId)) || null; }).filter(Boolean)
|
|
2557
|
+
: [];
|
|
2558
|
+
if (!collageId) {
|
|
2559
|
+
return { success: false, error: 'createLetterCollage returned no id', letterCount: letterIds.length, letterIds: letterIds };
|
|
2560
|
+
}
|
|
2561
|
+
return { success: true, collageId: collageId, itemId: collageId, letterCount: letterIds.length, letterIds: letterIds };
|
|
2364
2562
|
})();
|
|
2365
2563
|
`.trim();
|
|
2366
2564
|
}
|
|
@@ -4094,6 +4292,596 @@ ${mask ? ` app.imageTools.applyMask(raster, '${mask}');\n` : ''} const ite
|
|
|
4094
4292
|
return `(function() { return { error: 'Unknown query action: ${input.action}' }; })();`;
|
|
4095
4293
|
}
|
|
4096
4294
|
}
|
|
4295
|
+
// ===========================================================================
|
|
4296
|
+
// DEFORMATION
|
|
4297
|
+
// ===========================================================================
|
|
4298
|
+
generateDeform(input) {
|
|
4299
|
+
const guard = `if (!app.deformPresets) return { error: 'DeformPresets not available' };`;
|
|
4300
|
+
switch (input.action) {
|
|
4301
|
+
case 'apply': {
|
|
4302
|
+
if (!input.itemId)
|
|
4303
|
+
return `(function() { return { error: 'itemId is required for deform apply' }; })();`;
|
|
4304
|
+
if (!input.preset)
|
|
4305
|
+
return `(function() { return { error: 'preset is required for deform apply' }; })();`;
|
|
4306
|
+
const params = {};
|
|
4307
|
+
if (input.frequency !== undefined)
|
|
4308
|
+
params.frequency = input.frequency;
|
|
4309
|
+
if (input.amplitude !== undefined)
|
|
4310
|
+
params.amplitude = input.amplitude;
|
|
4311
|
+
if (input.phase)
|
|
4312
|
+
params.phase = input.phase;
|
|
4313
|
+
if (input.loop !== undefined)
|
|
4314
|
+
params.loop = input.loop;
|
|
4315
|
+
if (input.axis)
|
|
4316
|
+
params.axis = input.axis;
|
|
4317
|
+
if (input.turns !== undefined)
|
|
4318
|
+
params.turns = input.turns;
|
|
4319
|
+
if (input.waves !== undefined)
|
|
4320
|
+
params.waves = input.waves;
|
|
4321
|
+
if (input.maxDisplacement !== undefined)
|
|
4322
|
+
params.maxDisplacement = input.maxDisplacement;
|
|
4323
|
+
if (input.speed !== undefined)
|
|
4324
|
+
params.speed = input.speed;
|
|
4325
|
+
if (input.steps !== undefined)
|
|
4326
|
+
params.steps = input.steps;
|
|
4327
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4328
|
+
return `
|
|
4329
|
+
// Apply deformation preset
|
|
4330
|
+
(function() {
|
|
4331
|
+
${guard}
|
|
4332
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4333
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4334
|
+
app.deformPresets.apply(entry.item, '${input.preset}', ${JSON.stringify(params)});
|
|
4335
|
+
return { success: true, action: 'apply', itemId: ${itemIdStr}, preset: '${input.preset}' };
|
|
4336
|
+
})();`.trim();
|
|
4337
|
+
}
|
|
4338
|
+
case 'trigger': {
|
|
4339
|
+
if (!input.itemId)
|
|
4340
|
+
return `(function() { return { error: 'itemId is required for deform trigger' }; })();`;
|
|
4341
|
+
if (!input.preset)
|
|
4342
|
+
return `(function() { return { error: 'preset is required for deform trigger' }; })();`;
|
|
4343
|
+
const params = {};
|
|
4344
|
+
if (input.amplitude !== undefined)
|
|
4345
|
+
params.amplitude = input.amplitude;
|
|
4346
|
+
if (input.speed !== undefined)
|
|
4347
|
+
params.speed = input.speed;
|
|
4348
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4349
|
+
return `
|
|
4350
|
+
// Trigger one-shot deformation
|
|
4351
|
+
(function() {
|
|
4352
|
+
${guard}
|
|
4353
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4354
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4355
|
+
app.deformPresets.trigger(entry.item, '${input.preset}', ${JSON.stringify(params)});
|
|
4356
|
+
return { success: true, action: 'trigger', itemId: ${itemIdStr}, preset: '${input.preset}' };
|
|
4357
|
+
})();`.trim();
|
|
4358
|
+
}
|
|
4359
|
+
case 'remove': {
|
|
4360
|
+
if (!input.itemId)
|
|
4361
|
+
return `(function() { return { error: 'itemId is required for deform remove' }; })();`;
|
|
4362
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4363
|
+
return `
|
|
4364
|
+
// Remove deformation and restore geometry
|
|
4365
|
+
(function() {
|
|
4366
|
+
${guard}
|
|
4367
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4368
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4369
|
+
app.deformPresets.remove(entry.item);
|
|
4370
|
+
return { success: true, action: 'remove', itemId: ${itemIdStr} };
|
|
4371
|
+
})();`.trim();
|
|
4372
|
+
}
|
|
4373
|
+
default:
|
|
4374
|
+
return `(function() { return { error: 'Unknown deform action: ${input.action}' }; })();`;
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
// ===========================================================================
|
|
4378
|
+
// SPRITE SHEETS
|
|
4379
|
+
// ===========================================================================
|
|
4380
|
+
generateSpriteSheet(input) {
|
|
4381
|
+
const guard = `if (!app.spriteSheetSystem) return { error: 'SpriteSheetSystem not available' };`;
|
|
4382
|
+
switch (input.action) {
|
|
4383
|
+
case 'generate': {
|
|
4384
|
+
const opts = {};
|
|
4385
|
+
if (input.poses)
|
|
4386
|
+
opts.poses = input.poses;
|
|
4387
|
+
if (input.transition)
|
|
4388
|
+
opts.transition = input.transition;
|
|
4389
|
+
if (input.bakedAnimation)
|
|
4390
|
+
opts.bakedAnimation = input.bakedAnimation;
|
|
4391
|
+
if (input.animations)
|
|
4392
|
+
opts.animations = input.animations;
|
|
4393
|
+
if (input.padding !== undefined)
|
|
4394
|
+
opts.padding = input.padding;
|
|
4395
|
+
if (input.name)
|
|
4396
|
+
opts.name = input.name;
|
|
4397
|
+
return `
|
|
4398
|
+
// Generate sprite sheet from skeleton
|
|
4399
|
+
(async function() {
|
|
4400
|
+
${guard}
|
|
4401
|
+
const sheet = await app.spriteSheetSystem.generateSpriteSheet(${JSON.stringify(input.skeletonId || '')}, ${JSON.stringify(opts)});
|
|
4402
|
+
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 };
|
|
4403
|
+
})();`.trim();
|
|
4404
|
+
}
|
|
4405
|
+
case 'play': {
|
|
4406
|
+
const opts = {};
|
|
4407
|
+
if (input.x !== undefined)
|
|
4408
|
+
opts.x = input.x;
|
|
4409
|
+
if (input.y !== undefined)
|
|
4410
|
+
opts.y = input.y;
|
|
4411
|
+
if (input.animation)
|
|
4412
|
+
opts.animation = input.animation;
|
|
4413
|
+
if (input.fps !== undefined)
|
|
4414
|
+
opts.fps = input.fps;
|
|
4415
|
+
if (input.scale !== undefined)
|
|
4416
|
+
opts.scale = input.scale;
|
|
4417
|
+
return `
|
|
4418
|
+
// Play sprite sheet animation
|
|
4419
|
+
(async function() {
|
|
4420
|
+
${guard}
|
|
4421
|
+
const player = await app.spriteSheetSystem.playSpriteSheet(${JSON.stringify(input.spriteSheetId || '')}, ${JSON.stringify(opts)});
|
|
4422
|
+
return { success: true, action: 'play', playerId: player.id };
|
|
4423
|
+
})();`.trim();
|
|
4424
|
+
}
|
|
4425
|
+
case 'export':
|
|
4426
|
+
return `
|
|
4427
|
+
// Export sprite sheet
|
|
4428
|
+
(async function() {
|
|
4429
|
+
${guard}
|
|
4430
|
+
const result = await app.spriteSheetSystem.exportSpriteSheet(${JSON.stringify(input.spriteSheetId || '')}, { format: '${input.format || 'png'}', download: true, includeMetadata: true });
|
|
4431
|
+
return { success: true, action: 'export', format: '${input.format || 'png'}' };
|
|
4432
|
+
})();`.trim();
|
|
4433
|
+
default:
|
|
4434
|
+
return `(function() { return { error: 'Unknown sprite sheet action: ${input.action}' }; })();`;
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
// ===========================================================================
|
|
4438
|
+
// STORAGE
|
|
4439
|
+
// ===========================================================================
|
|
4440
|
+
generateStorage(input) {
|
|
4441
|
+
const guard = `if (!app.storageManager) return { error: 'StorageManager not available' };`;
|
|
4442
|
+
switch (input.action) {
|
|
4443
|
+
case 'save': {
|
|
4444
|
+
const name = input.name ? JSON.stringify(input.name) : "'Untitled Project'";
|
|
4445
|
+
const thumbnailCode = input.thumbnail
|
|
4446
|
+
? `const thumbnail = app.exportThumbnail ? app.exportThumbnail() : null;\n if (thumbnail) project.thumbnail = thumbnail;`
|
|
4447
|
+
: '';
|
|
4448
|
+
return `
|
|
4449
|
+
// Save project
|
|
4450
|
+
(async function() {
|
|
4451
|
+
${guard}
|
|
4452
|
+
const project = { name: ${name}, data: app.serialize() };
|
|
4453
|
+
${thumbnailCode}
|
|
4454
|
+
const projectId = await app.storageManager.saveProject(project);
|
|
4455
|
+
return { success: true, action: 'save', projectId, name: ${name} };
|
|
4456
|
+
})();`.trim();
|
|
4457
|
+
}
|
|
4458
|
+
case 'load': {
|
|
4459
|
+
const projId = JSON.stringify(input.projectId || '');
|
|
4460
|
+
return `
|
|
4461
|
+
// Load project
|
|
4462
|
+
(async function() {
|
|
4463
|
+
${guard}
|
|
4464
|
+
const project = await app.storageManager.loadProject(${projId});
|
|
4465
|
+
if (!project) return { error: 'Project not found: ' + ${projId} };
|
|
4466
|
+
app.deserialize(project.data);
|
|
4467
|
+
return { success: true, action: 'load', projectId: ${projId}, name: project.name };
|
|
4468
|
+
})();`.trim();
|
|
4469
|
+
}
|
|
4470
|
+
case 'list':
|
|
4471
|
+
return `
|
|
4472
|
+
// List saved projects
|
|
4473
|
+
(async function() {
|
|
4474
|
+
${guard}
|
|
4475
|
+
const projects = await app.storageManager.listProjects();
|
|
4476
|
+
return { success: true, action: 'list', projects: projects.map(p => ({ id: p.id, name: p.name, updatedAt: p.updatedAt })), count: projects.length };
|
|
4477
|
+
})();`.trim();
|
|
4478
|
+
case 'delete': {
|
|
4479
|
+
const projId = JSON.stringify(input.projectId || '');
|
|
4480
|
+
return `
|
|
4481
|
+
// Delete project
|
|
4482
|
+
(async function() {
|
|
4483
|
+
${guard}
|
|
4484
|
+
await app.storageManager.deleteProject(${projId});
|
|
4485
|
+
return { success: true, action: 'delete', projectId: ${projId} };
|
|
4486
|
+
})();`.trim();
|
|
4487
|
+
}
|
|
4488
|
+
default:
|
|
4489
|
+
return `(function() { return { error: 'Unknown storage action: ${input.action}' }; })();`;
|
|
4490
|
+
}
|
|
4491
|
+
}
|
|
4492
|
+
// ===========================================================================
|
|
4493
|
+
// INTERACTION
|
|
4494
|
+
// ===========================================================================
|
|
4495
|
+
generateInteraction(input) {
|
|
4496
|
+
const guard = `if (!app.interactionSystem) return { error: 'InteractionSystem not available' };`;
|
|
4497
|
+
switch (input.action) {
|
|
4498
|
+
case 'add_behavior': {
|
|
4499
|
+
const itemIdStr = JSON.stringify(input.itemId || '');
|
|
4500
|
+
const params = JSON.stringify(input.params || {});
|
|
4501
|
+
return `
|
|
4502
|
+
// Add behavior to item
|
|
4503
|
+
(function() {
|
|
4504
|
+
${guard}
|
|
4505
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4506
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4507
|
+
const behaviorId = app.interactionSystem.addContinuousBehavior(entry.item, '${input.behaviorType}', ${params});
|
|
4508
|
+
return { success: true, action: 'add_behavior', itemId: ${itemIdStr}, behaviorType: '${input.behaviorType}', behaviorId };
|
|
4509
|
+
})();`.trim();
|
|
4510
|
+
}
|
|
4511
|
+
case 'remove_behavior': {
|
|
4512
|
+
const itemIdStr = JSON.stringify(input.itemId || '');
|
|
4513
|
+
const behaviorIdStr = JSON.stringify(input.behaviorId || '');
|
|
4514
|
+
return `
|
|
4515
|
+
// Remove behavior from item
|
|
4516
|
+
(function() {
|
|
4517
|
+
${guard}
|
|
4518
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4519
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4520
|
+
app.interactionSystem.removeContinuousBehavior(entry.item, ${behaviorIdStr});
|
|
4521
|
+
return { success: true, action: 'remove_behavior', itemId: ${itemIdStr}, behaviorId: ${behaviorIdStr} };
|
|
4522
|
+
})();`.trim();
|
|
4523
|
+
}
|
|
4524
|
+
case 'trigger_action': {
|
|
4525
|
+
const params = JSON.stringify(input.params || {});
|
|
4526
|
+
return `
|
|
4527
|
+
// Trigger interaction action
|
|
4528
|
+
(function() {
|
|
4529
|
+
${guard}
|
|
4530
|
+
app.interactionSystem.triggerAction('${input.actionType}', ${params});
|
|
4531
|
+
return { success: true, action: 'trigger_action', actionType: '${input.actionType}' };
|
|
4532
|
+
})();`.trim();
|
|
4533
|
+
}
|
|
4534
|
+
case 'get_state':
|
|
4535
|
+
return `
|
|
4536
|
+
// Get interaction state
|
|
4537
|
+
(function() {
|
|
4538
|
+
${guard}
|
|
4539
|
+
const state = app.interactionSystem.getState ? app.interactionSystem.getState() : {};
|
|
4540
|
+
return { success: true, action: 'get_state', ...state };
|
|
4541
|
+
})();`.trim();
|
|
4542
|
+
default:
|
|
4543
|
+
return `(function() { return { error: 'Unknown interaction action: ${input.action}' }; })();`;
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
// ===========================================================================
|
|
4547
|
+
// WIDGET EXPORT
|
|
4548
|
+
// ===========================================================================
|
|
4549
|
+
generateExportWidget(input) {
|
|
4550
|
+
const opts = {};
|
|
4551
|
+
if (input.download !== undefined)
|
|
4552
|
+
opts.download = input.download;
|
|
4553
|
+
if (input.filename)
|
|
4554
|
+
opts.filename = input.filename;
|
|
4555
|
+
if (input.includeInteractions !== undefined)
|
|
4556
|
+
opts.includeInteractions = input.includeInteractions;
|
|
4557
|
+
if (input.minify !== undefined)
|
|
4558
|
+
opts.minify = input.minify;
|
|
4559
|
+
return `
|
|
4560
|
+
// Export widget (pp:PinePaper ontology JSON)
|
|
4561
|
+
(async function() {
|
|
4562
|
+
if (!app.exportWidget) return { error: 'Widget export not available' };
|
|
4563
|
+
const result = await app.exportWidget(${JSON.stringify(opts)});
|
|
4564
|
+
return { success: true, json: result.json, filename: result.filename, embedCode: result.embedCode, metadata: result.data?.metadata };
|
|
4565
|
+
})();`.trim();
|
|
4566
|
+
}
|
|
4567
|
+
generateExportWidgetHtml(input) {
|
|
4568
|
+
const opts = {};
|
|
4569
|
+
if (input.title)
|
|
4570
|
+
opts.title = input.title;
|
|
4571
|
+
if (input.download !== undefined)
|
|
4572
|
+
opts.download = input.download;
|
|
4573
|
+
return `
|
|
4574
|
+
// Export widget as self-contained HTML
|
|
4575
|
+
(async function() {
|
|
4576
|
+
if (!app.exportWidgetHTML) return { error: 'Widget HTML export not available' };
|
|
4577
|
+
const result = await app.exportWidgetHTML(${JSON.stringify(opts)});
|
|
4578
|
+
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 } };
|
|
4579
|
+
})();`.trim();
|
|
4580
|
+
}
|
|
4581
|
+
// ===========================================================================
|
|
4582
|
+
// DATA VISUALIZATION (CHARTS)
|
|
4583
|
+
// ===========================================================================
|
|
4584
|
+
generateChart(input) {
|
|
4585
|
+
const guard = `if (!app.chartSystem) return { error: 'ChartSystem not available' };`;
|
|
4586
|
+
switch (input.action) {
|
|
4587
|
+
case 'create': {
|
|
4588
|
+
if (!input.chartType)
|
|
4589
|
+
return `(function() { return { error: 'chartType is required for create' }; })();`;
|
|
4590
|
+
if (!input.data)
|
|
4591
|
+
return `(function() { return { error: 'data is required for create' }; })();`;
|
|
4592
|
+
const dataStr = JSON.stringify(input.data);
|
|
4593
|
+
const optsStr = JSON.stringify(input.options || {});
|
|
4594
|
+
return `
|
|
4595
|
+
// Create chart
|
|
4596
|
+
(function() {
|
|
4597
|
+
${guard}
|
|
4598
|
+
const group = app.createChart(${JSON.stringify(input.chartType)}, ${dataStr}, ${optsStr});
|
|
4599
|
+
if (!group) return { error: 'Failed to create chart' };
|
|
4600
|
+
const chartId = app.chartSystem.getLastChartId();
|
|
4601
|
+
return { success: true, action: 'create', chartType: ${JSON.stringify(input.chartType)}, chartId, dataPoints: ${input.data.length} };
|
|
4602
|
+
})();`.trim();
|
|
4603
|
+
}
|
|
4604
|
+
case 'update': {
|
|
4605
|
+
if (!input.chartId)
|
|
4606
|
+
return `(function() { return { error: 'chartId is required for update' }; })();`;
|
|
4607
|
+
const chartIdStr = JSON.stringify(input.chartId);
|
|
4608
|
+
const dataStr = JSON.stringify(input.data || []);
|
|
4609
|
+
const optsStr = JSON.stringify(input.options || {});
|
|
4610
|
+
return `
|
|
4611
|
+
// Update chart data
|
|
4612
|
+
(function() {
|
|
4613
|
+
${guard}
|
|
4614
|
+
const chart = app.chartSystem.getChart(${chartIdStr});
|
|
4615
|
+
if (!chart) return { error: 'Chart not found: ' + ${chartIdStr} };
|
|
4616
|
+
app.chartSystem.update(${chartIdStr}, ${dataStr}, ${optsStr});
|
|
4617
|
+
return { success: true, action: 'update', chartId: ${chartIdStr} };
|
|
4618
|
+
})();`.trim();
|
|
4619
|
+
}
|
|
4620
|
+
case 'reconfigure': {
|
|
4621
|
+
if (!input.chartId)
|
|
4622
|
+
return `(function() { return { error: 'chartId is required for reconfigure' }; })();`;
|
|
4623
|
+
const chartIdStr = JSON.stringify(input.chartId);
|
|
4624
|
+
const optsStr = JSON.stringify(input.options || {});
|
|
4625
|
+
return `
|
|
4626
|
+
// Reconfigure chart styling
|
|
4627
|
+
(function() {
|
|
4628
|
+
${guard}
|
|
4629
|
+
const chart = app.chartSystem.getChart(${chartIdStr});
|
|
4630
|
+
if (!chart) return { error: 'Chart not found: ' + ${chartIdStr} };
|
|
4631
|
+
app.chartSystem.reconfigure(${chartIdStr}, ${optsStr});
|
|
4632
|
+
return { success: true, action: 'reconfigure', chartId: ${chartIdStr} };
|
|
4633
|
+
})();`.trim();
|
|
4634
|
+
}
|
|
4635
|
+
case 'remove': {
|
|
4636
|
+
if (!input.chartId)
|
|
4637
|
+
return `(function() { return { error: 'chartId is required for remove' }; })();`;
|
|
4638
|
+
const chartIdStr = JSON.stringify(input.chartId);
|
|
4639
|
+
return `
|
|
4640
|
+
// Remove chart
|
|
4641
|
+
(function() {
|
|
4642
|
+
${guard}
|
|
4643
|
+
app.chartSystem.remove(${chartIdStr});
|
|
4644
|
+
return { success: true, action: 'remove', chartId: ${chartIdStr} };
|
|
4645
|
+
})();`.trim();
|
|
4646
|
+
}
|
|
4647
|
+
default:
|
|
4648
|
+
return `(function() { return { error: 'Unknown chart action: ${input.action}' }; })();`;
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
// ===========================================================================
|
|
4652
|
+
// MAGIC SYSTEM
|
|
4653
|
+
// ===========================================================================
|
|
4654
|
+
generateMagic(input) {
|
|
4655
|
+
const guard = `if (!app.magicSystem) return { error: 'MagicSystem not available' };`;
|
|
4656
|
+
switch (input.action) {
|
|
4657
|
+
case 'animate': {
|
|
4658
|
+
const opts = {};
|
|
4659
|
+
if (input.mood)
|
|
4660
|
+
opts.energy = input.mood;
|
|
4661
|
+
if (input.selectionOnly !== undefined)
|
|
4662
|
+
opts.selectionOnly = input.selectionOnly;
|
|
4663
|
+
return `
|
|
4664
|
+
// Magic auto-animate
|
|
4665
|
+
(async function() {
|
|
4666
|
+
${guard}
|
|
4667
|
+
const result = await app.magicSystem.autoAnimate(${JSON.stringify(opts)});
|
|
4668
|
+
return { success: true, action: 'animate', itemsAnimated: result.count, mood: result.energy, type: result.type };
|
|
4669
|
+
})();`.trim();
|
|
4670
|
+
}
|
|
4671
|
+
case 'remix': {
|
|
4672
|
+
const opts = {};
|
|
4673
|
+
if (input.selectionOnly !== undefined)
|
|
4674
|
+
opts.selectionOnly = input.selectionOnly;
|
|
4675
|
+
return `
|
|
4676
|
+
// Magic style remix
|
|
4677
|
+
(async function() {
|
|
4678
|
+
${guard}
|
|
4679
|
+
const result = await app.magicSystem.remixStyle(${JSON.stringify(opts)});
|
|
4680
|
+
return { success: true, action: 'remix', itemsRemixed: result.count, palette: result.palette };
|
|
4681
|
+
})();`.trim();
|
|
4682
|
+
}
|
|
4683
|
+
default:
|
|
4684
|
+
return `(function() { return { error: 'Unknown magic action: ${input.action}' }; })();`;
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
// ===========================================================================
|
|
4688
|
+
// PHYSICS (RIGID BODY SIMULATION)
|
|
4689
|
+
// ===========================================================================
|
|
4690
|
+
generatePhysics(input) {
|
|
4691
|
+
const guard = `if (!app.physicsWorld) return { error: 'PhysicsWorld not available' };`;
|
|
4692
|
+
switch (input.action) {
|
|
4693
|
+
case 'init': {
|
|
4694
|
+
const gravity = JSON.stringify(input.gravity || { x: 0, y: 980 });
|
|
4695
|
+
return `
|
|
4696
|
+
// Initialize physics world
|
|
4697
|
+
(function() {
|
|
4698
|
+
${guard}
|
|
4699
|
+
app.physicsWorld.init({ gravity: ${gravity} });
|
|
4700
|
+
return { success: true, action: 'init', gravity: ${gravity} };
|
|
4701
|
+
})();`.trim();
|
|
4702
|
+
}
|
|
4703
|
+
case 'add_body': {
|
|
4704
|
+
if (!input.itemId)
|
|
4705
|
+
return `(function() { return { error: 'itemId is required for add_body' }; })();`;
|
|
4706
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4707
|
+
const config = {};
|
|
4708
|
+
if (input.bodyType)
|
|
4709
|
+
config.type = input.bodyType;
|
|
4710
|
+
if (input.mass !== undefined)
|
|
4711
|
+
config.mass = input.mass;
|
|
4712
|
+
if (input.friction !== undefined)
|
|
4713
|
+
config.friction = input.friction;
|
|
4714
|
+
if (input.restitution !== undefined)
|
|
4715
|
+
config.restitution = input.restitution;
|
|
4716
|
+
if (input.fixedRotation !== undefined)
|
|
4717
|
+
config.fixedRotation = input.fixedRotation;
|
|
4718
|
+
if (input.shape)
|
|
4719
|
+
config.shape = input.shape;
|
|
4720
|
+
return `
|
|
4721
|
+
// Add physics body
|
|
4722
|
+
(function() {
|
|
4723
|
+
${guard}
|
|
4724
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4725
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4726
|
+
const ok = app.physicsWorld.addBody(${itemIdStr}, ${JSON.stringify(config)});
|
|
4727
|
+
return { success: ok, action: 'add_body', itemId: ${itemIdStr}, bodyType: ${JSON.stringify(input.bodyType || 'dynamic')} };
|
|
4728
|
+
})();`.trim();
|
|
4729
|
+
}
|
|
4730
|
+
case 'remove_body': {
|
|
4731
|
+
if (!input.itemId)
|
|
4732
|
+
return `(function() { return { error: 'itemId is required for remove_body' }; })();`;
|
|
4733
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4734
|
+
return `
|
|
4735
|
+
// Remove physics body
|
|
4736
|
+
(function() {
|
|
4737
|
+
${guard}
|
|
4738
|
+
app.physicsWorld.removeBody(${itemIdStr});
|
|
4739
|
+
return { success: true, action: 'remove_body', itemId: ${itemIdStr} };
|
|
4740
|
+
})();`.trim();
|
|
4741
|
+
}
|
|
4742
|
+
case 'apply_force': {
|
|
4743
|
+
if (!input.itemId)
|
|
4744
|
+
return `(function() { return { error: 'itemId is required for apply_force' }; })();`;
|
|
4745
|
+
if (!input.force)
|
|
4746
|
+
return `(function() { return { error: 'force is required for apply_force' }; })();`;
|
|
4747
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4748
|
+
return `
|
|
4749
|
+
// Apply continuous force
|
|
4750
|
+
(function() {
|
|
4751
|
+
${guard}
|
|
4752
|
+
app.physicsWorld.applyForce(${itemIdStr}, ${JSON.stringify(input.force)});
|
|
4753
|
+
return { success: true, action: 'apply_force', itemId: ${itemIdStr}, force: ${JSON.stringify(input.force)} };
|
|
4754
|
+
})();`.trim();
|
|
4755
|
+
}
|
|
4756
|
+
case 'apply_impulse': {
|
|
4757
|
+
if (!input.itemId)
|
|
4758
|
+
return `(function() { return { error: 'itemId is required for apply_impulse' }; })();`;
|
|
4759
|
+
if (!input.impulse)
|
|
4760
|
+
return `(function() { return { error: 'impulse is required for apply_impulse' }; })();`;
|
|
4761
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4762
|
+
return `
|
|
4763
|
+
// Apply instant impulse
|
|
4764
|
+
(function() {
|
|
4765
|
+
${guard}
|
|
4766
|
+
app.physicsWorld.applyImpulse(${itemIdStr}, ${JSON.stringify(input.impulse)});
|
|
4767
|
+
return { success: true, action: 'apply_impulse', itemId: ${itemIdStr}, impulse: ${JSON.stringify(input.impulse)} };
|
|
4768
|
+
})();`.trim();
|
|
4769
|
+
}
|
|
4770
|
+
case 'set_velocity': {
|
|
4771
|
+
if (!input.itemId)
|
|
4772
|
+
return `(function() { return { error: 'itemId is required for set_velocity' }; })();`;
|
|
4773
|
+
if (!input.velocity)
|
|
4774
|
+
return `(function() { return { error: 'velocity is required for set_velocity' }; })();`;
|
|
4775
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4776
|
+
return `
|
|
4777
|
+
// Set body velocity
|
|
4778
|
+
(function() {
|
|
4779
|
+
${guard}
|
|
4780
|
+
app.physicsWorld.setVelocity(${itemIdStr}, ${JSON.stringify(input.velocity)});
|
|
4781
|
+
return { success: true, action: 'set_velocity', itemId: ${itemIdStr}, velocity: ${JSON.stringify(input.velocity)} };
|
|
4782
|
+
})();`.trim();
|
|
4783
|
+
}
|
|
4784
|
+
case 'get_state': {
|
|
4785
|
+
if (!input.itemId)
|
|
4786
|
+
return `(function() { return { error: 'itemId is required for get_state' }; })();`;
|
|
4787
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4788
|
+
return `
|
|
4789
|
+
// Get physics body state
|
|
4790
|
+
(function() {
|
|
4791
|
+
${guard}
|
|
4792
|
+
const state = app.physicsWorld.getBodyState(${itemIdStr});
|
|
4793
|
+
if (!state) return { error: 'No physics body for item: ' + ${itemIdStr} };
|
|
4794
|
+
return { success: true, action: 'get_state', itemId: ${itemIdStr}, ...state };
|
|
4795
|
+
})();`.trim();
|
|
4796
|
+
}
|
|
4797
|
+
case 'create_ground': {
|
|
4798
|
+
const y = input.y !== undefined ? input.y : 500;
|
|
4799
|
+
const w = input.width !== undefined ? input.width : 2000;
|
|
4800
|
+
return `
|
|
4801
|
+
// Create ground plane
|
|
4802
|
+
(function() {
|
|
4803
|
+
${guard}
|
|
4804
|
+
const groundId = app.physicsWorld.createGround(${y}, ${w});
|
|
4805
|
+
return { success: true, action: 'create_ground', groundId, y: ${y}, width: ${w} };
|
|
4806
|
+
})();`.trim();
|
|
4807
|
+
}
|
|
4808
|
+
case 'create_joint': {
|
|
4809
|
+
if (!input.itemId)
|
|
4810
|
+
return `(function() { return { error: 'itemId is required for create_joint' }; })();`;
|
|
4811
|
+
if (!input.targetItemId)
|
|
4812
|
+
return `(function() { return { error: 'targetItemId is required for create_joint' }; })();`;
|
|
4813
|
+
if (!input.jointType)
|
|
4814
|
+
return `(function() { return { error: 'jointType is required for create_joint' }; })();`;
|
|
4815
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4816
|
+
const targetStr = JSON.stringify(input.targetItemId);
|
|
4817
|
+
const params = JSON.stringify({ type: input.jointType, ...(input.jointParams || {}) });
|
|
4818
|
+
return `
|
|
4819
|
+
// Create physics joint
|
|
4820
|
+
(function() {
|
|
4821
|
+
${guard}
|
|
4822
|
+
const joint = app.physicsWorld.createJoint(${itemIdStr}, ${targetStr}, ${params});
|
|
4823
|
+
if (!joint) return { error: 'Failed to create joint between ' + ${itemIdStr} + ' and ' + ${targetStr} };
|
|
4824
|
+
return { success: true, action: 'create_joint', itemId: ${itemIdStr}, targetItemId: ${targetStr}, jointType: ${JSON.stringify(input.jointType)} };
|
|
4825
|
+
})();`.trim();
|
|
4826
|
+
}
|
|
4827
|
+
default:
|
|
4828
|
+
return `(function() { return { error: 'Unknown physics action: ${input.action}' }; })();`;
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
// ===========================================================================
|
|
4832
|
+
// MEASUREMENT SYSTEM
|
|
4833
|
+
// ===========================================================================
|
|
4834
|
+
generateMeasurement(input) {
|
|
4835
|
+
const guard = `if (!app.measurementSystem) return { error: 'MeasurementSystem not available' };`;
|
|
4836
|
+
switch (input.action) {
|
|
4837
|
+
case 'set_rulers': {
|
|
4838
|
+
const enabled = input.enabled !== false;
|
|
4839
|
+
return `
|
|
4840
|
+
// Toggle rulers
|
|
4841
|
+
(function() {
|
|
4842
|
+
${guard}
|
|
4843
|
+
app.measurementSystem.setRulersVisible(${enabled});
|
|
4844
|
+
return { success: true, action: 'set_rulers', enabled: ${enabled} };
|
|
4845
|
+
})();`.trim();
|
|
4846
|
+
}
|
|
4847
|
+
case 'set_grid': {
|
|
4848
|
+
const enabled = input.enabled !== false;
|
|
4849
|
+
return `
|
|
4850
|
+
// Toggle grid
|
|
4851
|
+
(function() {
|
|
4852
|
+
${guard}
|
|
4853
|
+
app.measurementSystem.setGridVisible(${enabled});
|
|
4854
|
+
return { success: true, action: 'set_grid', enabled: ${enabled} };
|
|
4855
|
+
})();`.trim();
|
|
4856
|
+
}
|
|
4857
|
+
// No measurementSystem guard — reads from itemRegistry directly
|
|
4858
|
+
case 'get_dimensions': {
|
|
4859
|
+
if (!input.itemId)
|
|
4860
|
+
return `(function() { return { error: 'itemId is required for get_dimensions' }; })();`;
|
|
4861
|
+
const itemIdStr = JSON.stringify(input.itemId);
|
|
4862
|
+
return `
|
|
4863
|
+
// Get item dimensions
|
|
4864
|
+
(function() {
|
|
4865
|
+
const entry = app.itemRegistry.get(${itemIdStr});
|
|
4866
|
+
if (!entry || !entry.item) return { error: 'Item not found: ' + ${itemIdStr} };
|
|
4867
|
+
const b = entry.item.bounds;
|
|
4868
|
+
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 };
|
|
4869
|
+
})();`.trim();
|
|
4870
|
+
}
|
|
4871
|
+
case 'set_snap': {
|
|
4872
|
+
const enabled = input.enabled !== false;
|
|
4873
|
+
return `
|
|
4874
|
+
// Toggle snap to grid
|
|
4875
|
+
(function() {
|
|
4876
|
+
${guard}
|
|
4877
|
+
app.measurementSystem.setSnapToUnitEnabled(${enabled});
|
|
4878
|
+
return { success: true, action: 'set_snap', enabled: ${enabled} };
|
|
4879
|
+
})();`.trim();
|
|
4880
|
+
}
|
|
4881
|
+
default:
|
|
4882
|
+
return `(function() { return { error: 'Unknown measurement action: ${input.action}' }; })();`;
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4097
4885
|
}
|
|
4098
4886
|
// Export singleton instance
|
|
4099
4887
|
export const codeGenerator = new PinePaperCodeGenerator();
|