@pinepaper.studio/mcp-server 1.6.18 → 1.6.20
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.de.md +1 -1
- package/README.es.md +1 -1
- package/README.fr.md +1 -1
- package/README.hi.md +1 -1
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +179 -10
- package/README.pt-BR.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/agent/export-manager.js +2 -2
- package/dist/agent/session-manager.d.ts +8 -0
- package/dist/agent/session-manager.d.ts.map +1 -1
- package/dist/agent/session-manager.js +17 -2
- package/dist/agent/session-manager.js.map +1 -1
- package/dist/assets/asset-manager.d.ts +28 -1
- package/dist/assets/asset-manager.d.ts.map +1 -1
- package/dist/assets/asset-manager.js +65 -25
- package/dist/assets/asset-manager.js.map +1 -1
- package/dist/assets/index.d.ts +1 -3
- package/dist/assets/index.d.ts.map +1 -1
- package/dist/assets/index.js +1 -3
- package/dist/assets/index.js.map +1 -1
- package/dist/assets/repositories/iconify.js +1 -1
- package/dist/assets/repositories/iconify.js.map +1 -1
- package/dist/assets/repositories/openclipart.js +1 -1
- package/dist/assets/repositories/openclipart.js.map +1 -1
- package/dist/assets/repositories/svgrepo.js +2 -2
- package/dist/assets/repositories/svgrepo.js.map +1 -1
- package/dist/assets/types.d.ts +2 -2
- package/dist/assets/types.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.d.ts +25 -0
- package/dist/browser/puppeteer-controller.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.js +107 -1
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/build-info.json +3 -3
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/design/design-systems.d.ts +2 -0
- package/dist/design/design-systems.d.ts.map +1 -1
- package/dist/design/design-systems.js +2 -0
- package/dist/design/design-systems.js.map +1 -1
- package/dist/execution/canvas-state.d.ts.map +1 -1
- package/dist/execution/canvas-state.js +19 -43
- package/dist/execution/canvas-state.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -11
- package/dist/index.js.map +1 -1
- package/dist/ontology/design-graph.js +2 -2
- package/dist/ontology/design-graph.js.map +1 -1
- package/dist/ontology/upstream-vocabulary.d.ts +2 -2
- package/dist/ontology/upstream-vocabulary.js +2 -2
- package/dist/ontology/validator.js +1 -1
- package/dist/ontology/validator.js.map +1 -1
- package/dist/ontology/vocabulary.d.ts.map +1 -1
- package/dist/ontology/vocabulary.js +8 -0
- package/dist/ontology/vocabulary.js.map +1 -1
- package/dist/tools/choreograph.d.ts +64 -0
- package/dist/tools/choreograph.d.ts.map +1 -0
- package/dist/tools/choreograph.js +336 -0
- package/dist/tools/choreograph.js.map +1 -0
- package/dist/tools/compact-descriptions.d.ts.map +1 -1
- package/dist/tools/compact-descriptions.js +16 -15
- package/dist/tools/compact-descriptions.js.map +1 -1
- package/dist/tools/definitions.d.ts +10 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +705 -150
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/effect-check.d.ts +26 -0
- package/dist/tools/effect-check.d.ts.map +1 -0
- package/dist/tools/effect-check.js +129 -0
- package/dist/tools/effect-check.js.map +1 -0
- package/dist/tools/effect-class.d.ts +31 -0
- package/dist/tools/effect-class.d.ts.map +1 -0
- package/dist/tools/effect-class.js +170 -0
- package/dist/tools/effect-class.js.map +1 -0
- package/dist/tools/engine-lists.d.ts +20 -5
- package/dist/tools/engine-lists.d.ts.map +1 -1
- package/dist/tools/engine-lists.js +106 -4
- package/dist/tools/engine-lists.js.map +1 -1
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +22 -1
- package/dist/tools/engine-methods.js.map +1 -1
- package/dist/tools/engine-surface.d.ts +1 -1
- package/dist/tools/engine-surface.d.ts.map +1 -1
- package/dist/tools/engine-surface.js +232 -13
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/handlers/camera.js +8 -8
- package/dist/tools/handlers/camera.js.map +1 -1
- package/dist/tools/handlers/export.d.ts.map +1 -1
- package/dist/tools/handlers/export.js +84 -5
- package/dist/tools/handlers/export.js.map +1 -1
- package/dist/tools/handlers/font.js +10 -10
- package/dist/tools/handlers/font.js.map +1 -1
- package/dist/tools/handlers/generate.d.ts +12 -12
- package/dist/tools/handlers/maps.d.ts.map +1 -1
- package/dist/tools/handlers/maps.js +23 -3
- package/dist/tools/handlers/maps.js.map +1 -1
- package/dist/tools/handlers.d.ts +44 -0
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +413 -39
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +39 -26
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/shape-params.d.ts +2 -2
- package/dist/tools/shape-params.d.ts.map +1 -1
- package/dist/tools/shape-params.js +27 -2
- package/dist/tools/shape-params.js.map +1 -1
- package/dist/tools/stick-vocabulary.d.ts +1 -1
- package/dist/tools/stick-vocabulary.js +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +11 -0
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +91 -3
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +1544 -276
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +3203 -907
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +393 -53
- package/dist/types/schemas.js.map +1 -1
- package/manifest.json +46 -2
- package/package.json +4 -3
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
import { COMPACT_DESCRIPTIONS } from './compact-descriptions.js';
|
|
11
11
|
import { MINIMAL_DESCRIPTIONS } from './minimal-descriptions.js';
|
|
12
12
|
import { STICK_GAITS, STICK_POSES, STICK_SEQUENCES, STICK_EXPRESSIONS, STICK_PROPS } from './stick-vocabulary.js';
|
|
13
|
-
import { GeneratorNameSchema, ItemTypeSchema } from '../types/schemas.js';
|
|
14
|
-
import { DESIGN_MEDIA, DESIGN_MEDIA_APPLY } from './engine-lists.js';
|
|
15
|
-
import { EffectTypeSchema, SimpleAnimationTypeSchema, EasingSchema, KEYFRAME_EASINGS, EASING_DESCRIPTIONS, KEYFRAME_EASING_JSON_SCHEMA, GEOMETRY_OPERATIONS, THREAD_STITCHES, } from '../types/schemas.js';
|
|
13
|
+
import { AgentBatchOperationTypeSchema, GeneratorNameSchema, ItemTypeSchema } from '../types/schemas.js';
|
|
14
|
+
import { DESIGN_MEDIA, DESIGN_MEDIA_APPLY, FILTER_TYPES, FILTER_DOCS, LINEAGE_KINDS, STYLED_SCENE_STYLES, STYLED_SCENE_STYLE_INFO } from './engine-lists.js';
|
|
15
|
+
import { EffectTypeSchema, AgentExportFormatSchema, RelationTypeSchema, SimpleAnimationTypeSchema, EasingSchema, KEYFRAME_EASINGS, EASING_DESCRIPTIONS, KEYFRAME_EASING_JSON_SCHEMA, GEOMETRY_OPERATIONS, THREAD_STITCHES, } from '../types/schemas.js';
|
|
16
16
|
/**
|
|
17
17
|
* Vocabulary lists derived from Zod schemas so tool descriptions stay in
|
|
18
18
|
* sync automatically. The description-schema-parity test
|
|
@@ -27,9 +27,44 @@ const SIMPLE_ANIMATION_TYPES_LIST = SimpleAnimationTypeSchema.options.join(', ')
|
|
|
27
27
|
// (round 8 DD, 1.68) — derived now, so it cannot say what the schema refuses.
|
|
28
28
|
const KEYFRAME_EASINGS_LIST = EasingSchema.options.join(', ');
|
|
29
29
|
const EASING_LINES = KEYFRAME_EASINGS.map((e) => `- ${e}: ${EASING_DESCRIPTIONS[e]}`).join('\n');
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const DOORS = [
|
|
31
|
+
{ when: 'A scene or landscape drawn in an art style (cut paper, ink, watercolour, dither, flow, ASCII)', doors: [{ tool: 'pinepaper_styled_scene', text: 'pinepaper_styled_scene' }] },
|
|
32
|
+
{ when: 'Make THIS — an item, a character, the whole scene — look like a style (8-bit, blueprint, ink sketch, flat modern, print halftone, paper cutout…)', doors: [{ tool: 'pinepaper_look', text: 'pinepaper_look (describe first: the engine grades each look native / faithful / impression)' }] },
|
|
33
|
+
{ when: 'A story, several beats, a narrative from prose — a video or film in N parts, or a click-through widget', doors: [{ tool: 'pinepaper_story', text: "pinepaper_story (from_text; nav: 'scenes' for a VIDEO — a scene chain that cuts — 'panels' for a widget)" }, { tool: 'pinepaper_scene_playback', text: 'scene chains (pinepaper_scene_playback: cutSound, play)' }] },
|
|
34
|
+
{ when: 'An ORIGINAL character from a description (a hero with a cape, a round robot, a cat in glasses), or a cast of variants', doors: [{ tool: 'pinepaper_original_character', text: 'pinepaper_original_character (describe first, then create)' }] },
|
|
35
|
+
{ when: 'A character that acts (expressions, poses, walks)', doors: [{ tool: 'pinepaper_character', text: 'pinepaper_character' }, { tool: 'pinepaper_stick', text: 'pinepaper_stick' }, { tool: 'pinepaper_rigging', text: 'pinepaper_rigging' }, { tool: 'pinepaper_deform', text: 'squash and stretch via pinepaper_deform' }] },
|
|
36
|
+
{ when: 'A number that counts up (a KPI roll-up, a year counter, a timer)', doors: [{ tool: 'pinepaper_create_item', text: 'create_item text with counter: exact on every frame, steady width; year counters: counter {from: -30000, to: 2026, era: true}' }] },
|
|
37
|
+
{ when: 'Any item that moves with intent (pops in, hops, bounces, rolls, flies, peeks, lands)', doors: [{ tool: 'pinepaper_choreograph', text: 'pinepaper_choreograph: beats, not hand-timed keyframes' }] },
|
|
38
|
+
{ when: 'One element that becomes the next thing, and the next, with no cuts (a pill → a field → a dot → a card)', doors: [{ tool: 'pinepaper_morph_sequence', text: 'pinepaper_morph_sequence' }] },
|
|
39
|
+
{ when: 'A title or caption with craft', doors: [{ tool: 'pinepaper_text_style', text: 'pinepaper_text_style (display styles; cursive with animate writes itself on)' }, { tool: 'pinepaper_text_effect', text: 'pinepaper_text_effect' }] },
|
|
40
|
+
{ when: 'A poster or layout in an aesthetic, or a collage', doors: [{ tool: 'pinepaper_design_system', text: 'pinepaper_design_system (compose)' }, { tool: 'pinepaper_compose', text: 'a collage: pinepaper_compose' }] },
|
|
41
|
+
{ when: 'A painted, stitched, hatched, inked or cut-paper look on shapes or photos', doors: [{ tool: 'pinepaper_design_medium', text: 'pinepaper_design_medium' }, { tool: 'pinepaper_image_filter', text: 'photos also pinepaper_image_filter (watercolor, painterly)' }] },
|
|
42
|
+
{ when: 'A brand', doors: [{ tool: 'pinepaper_brand_kit', text: 'pinepaper_brand_kit set (first): every item made afterwards inherits it' }] },
|
|
43
|
+
{ when: 'Sound and music', doors: [{ tool: 'pinepaper_sound', text: 'pinepaper_sound' }, { tool: 'pinepaper_audio_beats', text: 'pinepaper_audio_beats' }, { tool: 'pinepaper_beat_cuts', text: 'pinepaper_beat_cuts' }] },
|
|
44
|
+
{ when: 'Particles that must survive save, export and the cloud (confetti bursts, a trail), or custom per-frame drawing', doors: [{ tool: 'pinepaper_emitter', text: 'pinepaper_emitter' }, { tool: 'pinepaper_render_hook', text: 'custom drawing: pinepaper_render_hook' }] },
|
|
45
|
+
{ when: 'A camera move through the piece, or 3D', doors: [{ tool: 'pinepaper_camera_director', text: 'pinepaper_camera_director' }, { tool: 'pinepaper_world3d', text: '3D: pinepaper_world3d' }, { tool: 'pinepaper_relight', text: 'pinepaper_relight' }] },
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* The door block for a host's tool list (D72; cloud request from a3). A host
|
|
49
|
+
* that offers only some tools passes their names: a row keeps only the doors
|
|
50
|
+
* that host has, and a row with none is dropped — so the model is never told
|
|
51
|
+
* to "use pinepaper_X" for a tool it cannot see. With no list, every door.
|
|
52
|
+
*/
|
|
53
|
+
export function buildDoorBlock(available) {
|
|
54
|
+
const has = available ? new Set(available) : null;
|
|
55
|
+
const rows = DOORS.map((d) => {
|
|
56
|
+
const doors = d.doors.filter((x) => !has || !x.tool || has.has(x.tool));
|
|
57
|
+
return doors.length ? `- ${d.when} → ${doors.map((x) => x.text).join('; ')}` : null;
|
|
58
|
+
}).filter(Boolean);
|
|
59
|
+
return [
|
|
60
|
+
'CHOOSE THE HIGHEST-LEVEL TOOL FIRST. Primitive shapes over a generator backdrop with a pulse is the stock result users reject; reach for it only when nothing below fits the brief.',
|
|
61
|
+
...rows,
|
|
62
|
+
'pinepaper_agent_batch_execute then does the glue in ONE call: extra items, keyframes, relations, masks, effects, playback.',
|
|
63
|
+
'If no tool reaches what the brief asks (for example, redrawing a subject in another art style), tell the user what is missing instead of handing over a lesser result as done.',
|
|
64
|
+
].join('\n');
|
|
65
|
+
}
|
|
66
|
+
/** The full door block (every tool), for the guide and a local server. */
|
|
67
|
+
export const CHOOSE_THE_DOOR = buildDoorBlock();
|
|
33
68
|
export const AI_AGENT_GUIDE = `⚠️ You are connected to PinePaper Studio via MCP tools. ALWAYS use these tools to create visual content.
|
|
34
69
|
NEVER create standalone HTML pages, React components, or web apps as a substitute. Do NOT use frontend design skills or CSS-only animations instead of PinePaper.
|
|
35
70
|
PinePaper tools ARE the implementation — they generate Paper.js/JavaScript code that runs on the canvas and produce Canvas 2D, SVG, MP4, GIF, PNG exports.
|
|
@@ -43,11 +78,14 @@ The generated code is shown in tool responses and can be copied into PinePaper's
|
|
|
43
78
|
|
|
44
79
|
⚠️ KNOWN CONFLICT: The "frontend-design" plugin instructs agents to generate HTML/CSS pages for visual content. This conflicts with PinePaper. If you keep generating HTML instead of calling PinePaper tools, the user should disable the frontend-design plugin.
|
|
45
80
|
|
|
81
|
+
${CHOOSE_THE_DOOR}
|
|
82
|
+
|
|
46
83
|
WORKFLOW — Create → Validate → Iterate:
|
|
47
84
|
|
|
48
85
|
1. pinepaper_agent_start_job (clearCanvas: true, canvasPreset)
|
|
49
|
-
2.
|
|
50
|
-
|
|
86
|
+
2. The craft tool(s) above that fit the brief, each called once.
|
|
87
|
+
Then pinepaper_agent_batch_execute — ONE call for the glue:
|
|
88
|
+
set_canvas_size → set_background → create items → keyframe_animate / animate → relation → apply_mask → apply_effect → play_timeline
|
|
51
89
|
3. pinepaper_agent_end_job — returns screenshot
|
|
52
90
|
4. Show screenshot to user for validation
|
|
53
91
|
5. If user wants changes → modify specific items or start a new job
|
|
@@ -59,13 +97,13 @@ If you restart the pipeline or re-call batch_execute, you DOUBLE every item on t
|
|
|
59
97
|
To fix mistakes: use modify/delete operations, or start a NEW job with clearCanvas: true.
|
|
60
98
|
|
|
61
99
|
RULES:
|
|
62
|
-
- NEVER restart the pipeline — one start_job, one batch_execute, one end_job
|
|
63
|
-
-
|
|
100
|
+
- NEVER restart the pipeline — one start_job, one batch_execute, one end_job; the craft tools go between them
|
|
101
|
+
- Primitive operations go in the ONE batch_execute — canvas, background, items, animation, masks, effects, playback
|
|
64
102
|
- EXCEPTION: Diagrams (flowcharts, UML) use pinepaper_create_diagram_shape + pinepaper_connect, NOT batch_execute
|
|
65
|
-
-
|
|
103
|
+
- A generator is a BACKDROP: under a composed scene it adds texture; on its own it is the stock look. Prefer a styled scene, a drawn world or a real photo for the ground of a piece
|
|
66
104
|
- Use "$0", "$1" to reference items by creation order within the batch
|
|
67
105
|
|
|
68
|
-
─── BATCH OPERATIONS (
|
|
106
|
+
─── BATCH OPERATIONS (${AgentBatchOperationTypeSchema.options.length} types, in order) ───
|
|
69
107
|
|
|
70
108
|
CANVAS: set_canvas_size (width/height or preset) → set_background (color) → execute_generator (procedural art)
|
|
71
109
|
ITEMS: create (itemType, position, properties) → modify (itemId, properties) → delete (itemId) → group (itemIds → one entity)
|
|
@@ -73,9 +111,9 @@ ANIMATE: animate (loop presets) → keyframe_animate (timed reveals) → relatio
|
|
|
73
111
|
EFFECTS: apply_mask (reveal animations) → apply_effect (sparkle/blast/bubbles/fireflies/trail...)
|
|
74
112
|
PLAY: play_timeline (start playback)
|
|
75
113
|
|
|
76
|
-
─── GENERATORS (
|
|
114
|
+
─── GENERATORS (procedural backdrops — under a scene, not as one) ───
|
|
77
115
|
|
|
78
|
-
Generators fill the canvas with procedural art
|
|
116
|
+
Generators fill the canvas with procedural art: richer than a flat colour as a backdrop, but not a composition by themselves.
|
|
79
117
|
Choose based on mood:
|
|
80
118
|
Dreamy/soft: drawBokeh, drawGradientMesh, drawOrganicFlow
|
|
81
119
|
Techy/modern: drawCircuit, drawGrid, drawWindField
|
|
@@ -111,7 +149,7 @@ Text: text (content, fontSize, fontFamily, color, fontWeight)
|
|
|
111
149
|
All items: opacity, shadowColor, shadowBlur, blendMode, strokeColor, strokeWidth
|
|
112
150
|
|
|
113
151
|
SVG imports for recognizable objects (planes, cars, animals, buildings):
|
|
114
|
-
pinepaper_search_assets → pinepaper_import_asset (
|
|
152
|
+
pinepaper_search_assets → pinepaper_import_asset (open SVG icon repositories)
|
|
115
153
|
An imported SVG is ONE opaque item. To move/recolor its parts (a wheel, a window), pinepaper_group action:"break_apart" splits it into individually movable, re-grouped sub-parts.
|
|
116
154
|
|
|
117
155
|
Photos / raster images (on-device, no upload):
|
|
@@ -197,7 +235,7 @@ Measurement: pinepaper_measurement — rulers, grid, snap-to-grid, get item dime
|
|
|
197
235
|
|
|
198
236
|
─── CANVAS & EXPORT ───
|
|
199
237
|
|
|
200
|
-
|
|
238
|
+
Canvas presets (pinepaper_set_canvas_size preset): instagram-post (1080x1080), instagram-story (1080x1920), tiktok (1080x1920), youtube-thumbnail (1280x720), full-hd-1080p (1920x1080), twitter-post (1200x675). Platform names (instagram, youtube) are agent_export's, not canvas presets.
|
|
201
239
|
Export: pinepaper_agent_export (SVG/PNG/GIF/MP4/WebM/PDF, plus WAV for the soundtrack alone), pinepaper_export_svg
|
|
202
240
|
Widget: pinepaper_export_widget (pp:PinePaper ontology JSON), pinepaper_export_widget_html (self-contained HTML with tree-shaken runtime)`;
|
|
203
241
|
/*
|
|
@@ -255,13 +293,11 @@ USE WHEN:
|
|
|
255
293
|
- Custom canvas size requirements
|
|
256
294
|
- Design elements extend beyond current canvas bounds
|
|
257
295
|
|
|
258
|
-
COMMON PRESETS:
|
|
259
|
-
- instagram-
|
|
260
|
-
-
|
|
261
|
-
-
|
|
262
|
-
-
|
|
263
|
-
- hd-landscape: 1920x1080
|
|
264
|
-
- hd-portrait: 1080x1920
|
|
296
|
+
COMMON PRESETS (studio keys; an unknown key is refused with the full list):
|
|
297
|
+
- instagram-post: 1080x1080 · instagram-portrait · instagram-story: 1080x1920
|
|
298
|
+
- youtube-thumbnail: 1280x720 · youtube-short · tiktok: 1080x1920
|
|
299
|
+
- twitter-post: 1200x675 · facebook-post · linkedin-post · pinterest-pin
|
|
300
|
+
- full-hd-1080p: 1920x1080 · hd-720p: 1280x720 · a4-portrait / a4-landscape and other print sizes
|
|
265
301
|
|
|
266
302
|
Max canvas size: 16384 per side (a 12000 px DOOH or projection canvas fits); above 8192 Safari / iOS may not render it, Chrome and Firefox do. For wedding invitations, event cards, or detailed designs, use at least 1080x1080 or larger.`,
|
|
267
303
|
inputSchema: {
|
|
@@ -271,10 +307,10 @@ Max canvas size: 16384 per side (a 12000 px DOOH or projection canvas fits); abo
|
|
|
271
307
|
height: { type: 'number', description: 'Canvas height in pixels (100-16384)' },
|
|
272
308
|
preset: {
|
|
273
309
|
type: 'string',
|
|
274
|
-
description:
|
|
310
|
+
description: "A studio preset key instead of width/height, e.g. 'instagram-post', 'instagram-story', 'youtube-thumbnail', 'tiktok', 'full-hd-1080p'. An unknown key is refused with the known ones.",
|
|
275
311
|
},
|
|
276
312
|
},
|
|
277
|
-
|
|
313
|
+
// width + height, or preset alone (enforced by the Zod schema).
|
|
278
314
|
},
|
|
279
315
|
},
|
|
280
316
|
{
|
|
@@ -558,6 +594,9 @@ For glossy 3D spheres, use pinepaper_create_glossy_sphere instead. For diagonal
|
|
|
558
594
|
inputSchema: {
|
|
559
595
|
type: 'object',
|
|
560
596
|
properties: {
|
|
597
|
+
screenSpace: { type: 'boolean', description: 'HUD: true keeps the item fixed in the FRAME under camera moves; its x/y, keyframes and relations are then frame coordinates. Exports (mp4, webm, gif, png, pdf) honour it; the live preview still tilts it; SVG and Lottie ignore it (agent_export warns). A studio without it refuses.' },
|
|
598
|
+
counter: { description: "TEXT only: a number that counts on the scene clock, exact on every exported frame. {from, to, start? (s), duration? (s, 1), easing? (engine name or [x1,y1,x2,y2]), decimals?, prefix?, suffix?, locale?, grouping?, padStart?, steps?, tabular? (true: steady width)}; null removes it. A year counter: {from: -30000, to: 2026, era: true} reads '30,000 BCE' … '2026 CE' (eraNegative / eraPositive relabel; no year 0); a KPI: {from: 1.67, to: 5.58, suffix: '%'}. A studio without counters refuses.", oneOf: [{ type: 'object', additionalProperties: true }, { type: 'null' }] },
|
|
599
|
+
stepTiming: { description: "Animate 'on twos': N or {every: N, baseFps?} (every N frames of a 30 fps base unless baseFps), {fps}, or null to turn off. A stepped group steps its children; follows, spring_follow, auras, rigging and GPU generators are not stepped. A studio without it refuses.", oneOf: [{ type: 'integer', minimum: 1 }, { type: 'object', properties: { every: { type: 'integer', minimum: 1 }, baseFps: { type: 'number' }, fps: { type: 'number' } } }, { type: 'null' }] },
|
|
561
600
|
itemType: {
|
|
562
601
|
type: 'string',
|
|
563
602
|
enum: ['text', 'circle', 'star', 'rectangle', 'triangle', 'polygon', 'ellipse', 'path', 'line', 'arc', 'pentagon', 'hexagon', 'diamond', 'arrow', 'heart', 'disk', 'circle-outline', 'arrow-right', 'speech-bubble', 'speech-bubble-square', 'speech-bubble-pointed', 'thought-bubble', 'double-bubble', 'quote-bubble', 'comment-box', 'callout-box', 'shader', 'field'],
|
|
@@ -716,6 +755,9 @@ Use the collage group's id for palette/collageStyle; use a child letter's id for
|
|
|
716
755
|
inputSchema: {
|
|
717
756
|
type: 'object',
|
|
718
757
|
properties: {
|
|
758
|
+
screenSpace: { type: 'boolean', description: 'HUD: true keeps the item fixed in the FRAME under camera moves; its x/y, keyframes and relations are then frame coordinates. Exports (mp4, webm, gif, png, pdf) honour it; the live preview still tilts it; SVG and Lottie ignore it (agent_export warns). A studio without it refuses.' },
|
|
759
|
+
counter: { description: "TEXT only: a number that counts on the scene clock, exact on every exported frame. {from, to, start? (s), duration? (s, 1), easing? (engine name or [x1,y1,x2,y2]), decimals?, prefix?, suffix?, locale?, grouping?, padStart?, steps?, tabular? (true: steady width)}; null removes it. A year counter: {from: -30000, to: 2026, era: true} reads '30,000 BCE' … '2026 CE' (eraNegative / eraPositive relabel; no year 0); a KPI: {from: 1.67, to: 5.58, suffix: '%'}. A studio without counters refuses.", oneOf: [{ type: 'object', additionalProperties: true }, { type: 'null' }] },
|
|
760
|
+
stepTiming: { description: "Animate 'on twos': N or {every: N, baseFps?} (every N frames of a 30 fps base unless baseFps), {fps}, or null to turn off. A stepped group steps its children; follows, spring_follow, auras, rigging and GPU generators are not stepped. A studio without it refuses.", oneOf: [{ type: 'integer', minimum: 1 }, { type: 'object', properties: { every: { type: 'integer', minimum: 1 }, baseFps: { type: 'number' }, fps: { type: 'number' } } }, { type: 'null' }] },
|
|
719
761
|
itemId: {
|
|
720
762
|
type: 'string',
|
|
721
763
|
description: 'Registry ID of the item (e.g., "item_1")',
|
|
@@ -1075,7 +1117,7 @@ EXAMPLE (sequenceDiagram):
|
|
|
1075
1117
|
"mermaidText": "sequenceDiagram\\n Alice->>Bob: Hello\\n Bob-->>Alice: Hi back\\n Note right of Bob: Thinking..."
|
|
1076
1118
|
}
|
|
1077
1119
|
|
|
1078
|
-
Returns { success, nodeCount, edgeCount, nodes, edges, errors }.
|
|
1120
|
+
Returns { success, nodeCount, edgeCount, nodeItemIds, nodeIdMap?, edgeIds?, nodes, edges, errors }. nodeItemIds are the CANVAS ids of the shapes (item_N): pass THOSE to pinepaper_auto_layout, pinepaper_animate, pinepaper_add_relation. The mermaid ids (A, B…) in edges[].from/to name nodes in the source text and resolve to nothing on the canvas; nodeIdMap (where the studio returns it) maps one to the other, and edgeIds are the connectors' canvas ids.
|
|
1079
1121
|
|
|
1080
1122
|
NOTE: Import-only — there is no MCP equivalent of FxTool's exportMermaid. Mermaid lacks animation primitives, so a round-trip would silently drop animations.`,
|
|
1081
1123
|
inputSchema: {
|
|
@@ -1106,7 +1148,7 @@ NOTE: Import-only — there is no MCP equivalent of FxTool's exportMermaid. Merm
|
|
|
1106
1148
|
idempotentHint: false,
|
|
1107
1149
|
openWorldHint: true,
|
|
1108
1150
|
},
|
|
1109
|
-
description: `Import a raster image from a URL
|
|
1151
|
+
description: `Import a raster image onto the canvas from an http(s) URL, a data: URL, or a LOCAL FILE PATH (the server reads the file). Returns an itemId for the imported image.
|
|
1110
1152
|
|
|
1111
1153
|
USE WHEN:
|
|
1112
1154
|
- User wants to add a photo, PNG, JPG, or other raster image
|
|
@@ -1128,6 +1170,9 @@ Apply a mask shape during import to clip the image:
|
|
|
1128
1170
|
|
|
1129
1171
|
SIZE CONSTRAINTS:
|
|
1130
1172
|
Use maxWidth/maxHeight to constrain the image dimensions while preserving aspect ratio.
|
|
1173
|
+
- maxEdge: the longest edge the studio STORES the image at (px), or 'native' to keep every pixel. The result says when it was stored smaller.
|
|
1174
|
+
- nativeSize: true places the image at its source footprint even if it was stored smaller.
|
|
1175
|
+
- smoothing: 'off' (nearest-neighbour, for pixel art) | 'low' (default) | 'medium' | 'high'.
|
|
1131
1176
|
|
|
1132
1177
|
EXAMPLES:
|
|
1133
1178
|
- Import photo: url: "https://example.com/photo.jpg"
|
|
@@ -1184,7 +1229,7 @@ EXAMPLES:
|
|
|
1184
1229
|
idempotentHint: true,
|
|
1185
1230
|
openWorldHint: true,
|
|
1186
1231
|
},
|
|
1187
|
-
description: `Search for free SVG assets from open repositories (
|
|
1232
|
+
description: `Search for free SVG assets from open repositories (Iconify, Font Awesome). The result's sources says how each repository answered (ok | timeout | error); one that is down does not hold up the others, and a search that names one repository fails if it is down.
|
|
1188
1233
|
|
|
1189
1234
|
USE WHEN:
|
|
1190
1235
|
- User wants icons, illustrations, or clipart
|
|
@@ -1194,15 +1239,13 @@ USE WHEN:
|
|
|
1194
1239
|
- Need graphics that are too complex to draw with paths
|
|
1195
1240
|
|
|
1196
1241
|
REPOSITORIES:
|
|
1197
|
-
- SVGRepo: 500,000+ icons, various categories and licenses
|
|
1198
|
-
- OpenClipart: 150,000+ public domain clipart
|
|
1199
1242
|
- Iconify: 200,000+ icon sets (various licenses)
|
|
1200
1243
|
- Font Awesome: 2,000+ free icons (CC BY 4.0)
|
|
1201
1244
|
|
|
1202
1245
|
WORKFLOW:
|
|
1203
1246
|
1. Search for assets: pinepaper_search_assets(query: "rocket")
|
|
1204
1247
|
2. Review results (you'll see assetIds, previews, licenses)
|
|
1205
|
-
3. Import chosen asset: pinepaper_import_asset(assetId: "
|
|
1248
|
+
3. Import chosen asset: pinepaper_import_asset(assetId: "iconify_mdi_rocket")
|
|
1206
1249
|
|
|
1207
1250
|
IMPORTANT NOTES:
|
|
1208
1251
|
- Always respect license terms (check license field in results)
|
|
@@ -1239,7 +1282,7 @@ SVG CONTENT RETRIEVAL:
|
|
|
1239
1282
|
},
|
|
1240
1283
|
repository: {
|
|
1241
1284
|
type: 'string',
|
|
1242
|
-
enum: ['all', '
|
|
1285
|
+
enum: ['all', 'iconify', 'fontawesome'],
|
|
1243
1286
|
description: 'Which repository to search (default: all)',
|
|
1244
1287
|
},
|
|
1245
1288
|
limit: {
|
|
@@ -1273,12 +1316,12 @@ USE WHEN:
|
|
|
1273
1316
|
WORKFLOW:
|
|
1274
1317
|
1. Search: pinepaper_search_assets(query: "rocket")
|
|
1275
1318
|
2. Choose: Review results, pick one with desired license
|
|
1276
|
-
3. Import: pinepaper_import_asset(assetId: "
|
|
1319
|
+
3. Import: pinepaper_import_asset(assetId: "iconify_mdi_rocket", position: {x: 400, y: 300})
|
|
1277
1320
|
4. Modify: Use pinepaper_modify_item to adjust size, color, etc.
|
|
1278
1321
|
5. Animate: Use pinepaper_animate or pinepaper_add_relation for motion
|
|
1279
1322
|
|
|
1280
1323
|
PARAMETERS:
|
|
1281
|
-
- assetId: From search results (e.g., "
|
|
1324
|
+
- assetId: From search results (e.g., "iconify_mdi_rocket")
|
|
1282
1325
|
- url: Alternative to assetId - direct URL to SVG
|
|
1283
1326
|
- position: Where to place on canvas (default: center)
|
|
1284
1327
|
- scale: Size multiplier (default: 1.0)
|
|
@@ -1297,9 +1340,9 @@ CACHING:
|
|
|
1297
1340
|
- No disk storage (memory only)
|
|
1298
1341
|
|
|
1299
1342
|
EXAMPLES:
|
|
1300
|
-
- Import from search: {assetId: "
|
|
1343
|
+
- Import from search: {assetId: "iconify_mdi_rocket", position: {x: 400, y: 300}, scale: 2.0}
|
|
1301
1344
|
- Import from URL: {url: "https://example.com/icon.svg", position: {x: 500, y: 400}}
|
|
1302
|
-
- Import with color override: {assetId: "
|
|
1345
|
+
- Import with color override: {assetId: "iconify_mdi_heart", position: {x: 400, y: 300}, color: "#ef4444"}
|
|
1303
1346
|
|
|
1304
1347
|
RETURNS:
|
|
1305
1348
|
- itemId: ID of the imported SVG group (use for further modifications)
|
|
@@ -1309,7 +1352,7 @@ RETURNS:
|
|
|
1309
1352
|
properties: {
|
|
1310
1353
|
assetId: {
|
|
1311
1354
|
type: 'string',
|
|
1312
|
-
description: 'Asset ID from search results (e.g., "
|
|
1355
|
+
description: 'Asset ID from search results (e.g., "iconify_mdi_rocket")',
|
|
1313
1356
|
},
|
|
1314
1357
|
url: {
|
|
1315
1358
|
type: 'string',
|
|
@@ -1636,6 +1679,7 @@ RETURNS: create → { success, action, eventId }; pulse → { success, action, e
|
|
|
1636
1679
|
inputSchema: {
|
|
1637
1680
|
type: 'object',
|
|
1638
1681
|
properties: {
|
|
1682
|
+
at: { description: 'create: fire this event at these scene times (seconds) — a timed trigger that runs on the timeline and in video exports. The widget player does not run timed triggers.', oneOf: [{ type: 'number' }, { type: 'array', items: { type: 'number' } }] },
|
|
1639
1683
|
action: { type: 'string', enum: ['create', 'pulse'], description: "'create' a pp:event · 'pulse' it to fire" },
|
|
1640
1684
|
name: { type: 'string', description: "Event name — required for action 'create'" },
|
|
1641
1685
|
payloadType: { type: 'string', description: "Payload type label (default 'Pulse') — action 'create'" },
|
|
@@ -2117,7 +2161,7 @@ MODES:
|
|
|
2117
2161
|
|
|
2118
2162
|
This complements pinepaper_validate_design (which validates a definition object, not the live scene).
|
|
2119
2163
|
|
|
2120
|
-
EXAMPLE (pre-flight): { ops: [ { kind: "create", id: "c1", itemType: "circle" }, { kind: "addRelation",
|
|
2164
|
+
EXAMPLE (pre-flight): { ops: [ { kind: "create", id: "c1", itemType: "circle" }, { kind: "addRelation", from: "c1", to: "missing", relation: "orbits" } ] } (source / target / relationType are accepted as the same fields)`,
|
|
2121
2165
|
inputSchema: {
|
|
2122
2166
|
type: 'object',
|
|
2123
2167
|
properties: {
|
|
@@ -2216,7 +2260,7 @@ ACTIONS:
|
|
|
2216
2260
|
- set_clip: { id, inPoint, outPoint (media-time s, outPoint > inPoint), timeOffset? (canvas s: also MOVE the clip to start there) } → re-trim an ALREADY-uploaded clip (video or audio). A studio that cannot move clips refuses timeOffset rather than ignoring it.
|
|
2217
2261
|
- split: { id, at (canvas s) } → cut a clip in two: { leftId, rightId, leftMediaId, rightMediaId, at }. The first half keeps its id. Refused (not claimed) where the studio cannot split or the time is outside the clip.
|
|
2218
2262
|
- add_transition: { fromItemId (ends at the cut), toItemId (starts at it), transition?: { type: 'crossfade'|'dip', seconds, color } } → the same in preview and export. Split a clip, then transition between its halves, or join two clips that meet.
|
|
2219
|
-
- For an IMAGE on the timeline,
|
|
2263
|
+
- For an IMAGE on the timeline, import it with pinepaper_import_image, then give it bornAt / ttl (seconds on screen) with pinepaper_modify_item.
|
|
2220
2264
|
|
|
2221
2265
|
NOTES:
|
|
2222
2266
|
- The server fetches the URL (or reads a local path / data: URL) and hands the page the bytes, so it does not need to be CORS-permitted.
|
|
@@ -2293,6 +2337,10 @@ ACTIONS:
|
|
|
2293
2337
|
- apply_style: { itemId, styleKey, palette?, variant?, content?, fontFamily?, fontSize? } — render a text item as a layered display title (offset copies, outlines, fills). The result GROUP ADOPTS the text's registry id, so existing relations/keyframes/handles keep pointing at it. fontFamily: 'suggested' opts into the face the style was designed around (arcade wants a pixel face — a layer stack can outline any typeface but cannot pixellate one); never automatic.
|
|
2294
2338
|
- set_font_axes: { itemId, axes: { weight?, width?, slant? } } — STANDARD variable-font axes only (Canvas 2D has no font-variation-settings, so custom foundry axes are unreachable — platform limit, stated, not silent). Returns {applied, rejected}: CHECK rejected — an axis silently ignored is the failure this surface exists to prevent. All three are ordinary animatable properties: keyframe fontWeight and type breathes between weights.
|
|
2295
2339
|
- list_styles: {} — styles + palettes + axes, for pickers. Call this first; styleKey strings come from here.
|
|
2340
|
+
- list: {} — the same as list_styles.
|
|
2341
|
+
- cursive: { text, cursiveOptions?: { x?, y?, scale? (1), strokeColor? ('#ffffff'), strokeWidth? (2), nib? ('monoline' | 'broad' | {angle?, width?, contrast?, taper?}), animate? (false), duration? (2), easing? ('easeOut'), timeOffset? (0), singleStroke?, showGhost?, tension? } } → { itemId, strokes, width } — writes the words as STROKED handwriting, a path rather than a glyph. animate: true writes it on by itself over duration seconds (from timeOffset), no keyframes needed; otherwise pinepaper_animate's draw-on applies to it. Unknown cursiveOptions keys are refused.
|
|
2342
|
+
- wrap: { itemId, maxWidth } — breaks a text item into lines no wider than maxWidth (canvas units). unwrap: { itemId } — restores the single line; wrap is not destructive.
|
|
2343
|
+
- to_collage: { itemId, text?, collageOptions? } — rebuilds a text item as a letter collage (same vocabulary as pinepaper_create_letter_collage); text defaults to the item's content.
|
|
2296
2344
|
|
|
2297
2345
|
OVER PHOTOGRAPHS, PASS A PALETTE — NEVER THE DEFAULT. Measured on a real
|
|
2298
2346
|
render of captions over photographs: highlighter, sticker, caption and badge
|
|
@@ -2314,7 +2362,7 @@ EXAMPLE: { action: 'apply_style', itemId: 'title_1', styleKey: 'arcade', fontFam
|
|
|
2314
2362
|
action: { type: 'string', enum: ['apply_style', 'set_font_axes', 'list_styles', 'list', 'cursive', 'wrap', 'unwrap', 'to_collage'], description: "Text style operation. 'list' is accepted as an alias for 'list_styles', because pinepaper_text_effect spells the same action 'list' and an agent moving between them guesses wrong. 'cursive' draws text as STROKED handwriting — a path, not a glyph, so draw-on animation and outline_stroke both apply. 'wrap'/'unwrap' break a text item to a width, reversibly. 'to_collage' converts an EXISTING text item into a letter collage in place." },
|
|
2315
2363
|
text: { type: 'string', description: 'cursive: the words to write. to_collage: the text to build from, if it differs from the item.' },
|
|
2316
2364
|
maxWidth: { type: 'number', description: 'wrap: the width to break at, in canvas units. unwrap restores the original, so this is not destructive.' },
|
|
2317
|
-
cursiveOptions: { type: 'object', description:
|
|
2365
|
+
cursiveOptions: { type: 'object', description: "cursive: { x?, y?, scale?, strokeColor?, strokeWidth?, nib?, animate?, duration?, easing?, timeOffset?, singleStroke?, showGhost?, tension? }. animate: true writes the line on over duration seconds. nib: 'monoline' for one even line, 'broad' (or {angle, width, contrast, taper}) for thick/thin; omitted, the studio's default pen." },
|
|
2318
2366
|
collageOptions: { type: 'object', description: 'to_collage: style, palette and the rest — same vocabulary as pinepaper_create_letter_collage.' },
|
|
2319
2367
|
itemId: { type: 'string', description: 'Text item id — apply_style / set_font_axes.' },
|
|
2320
2368
|
styleKey: { type: 'string', description: 'apply_style: style name from list_styles.' },
|
|
@@ -2352,7 +2400,7 @@ READ THIS BEFORE PROMISING A MEDIUM. Naming a medium does not create its marks.
|
|
|
2352
2400
|
Call resolve before telling a user you will render in a medium. "yes, but as an impression" is the true answer for half of them, and a flat approximation misrepresents a medium rather than approximating it.
|
|
2353
2401
|
|
|
2354
2402
|
ACTIONS:
|
|
2355
|
-
- apply: { itemId, medium, options? } — render an item in ANY medium the engine can make: ${Object.keys(DESIGN_MEDIA_APPLY).join(', ')}. One engine entry point; a medium
|
|
2403
|
+
- apply: { itemId, medium, options? } — on a PHOTO (an image item), watercolor and oil run as the watercolor / painterly image filters and report fidelity 'stylised' (an image filter, not paint simulated over geometry); other media are refused for photos, with the reason. On shapes — render an item in ANY medium the engine can make: ${Object.keys(DESIGN_MEDIA_APPLY).join(', ')}. One engine entry point; a medium this studio cannot apply is refused with the reason: pass it on. ink / charcoal (where the studio applies them) restyle a path, text, or group of them IN PLACE — same itemId, keyframes and relations kept — with a boiling hand-drawn line; raster and video exports boil, SVG/Lottie get the colour and width only. Their options: color, amplitude (px), wavelength (px), fps (boil rate, 12), seed, boil (default true), width (px, paths), keepColor. watercolor options: color, brush, water 0..1, opacity, edge 0..1 (the dark rim), angle, bleed, seed. oil (stylised): color, brush, length, density, toneRange, relief, angle, seed. cutPaper (vector: survives SVG/PDF/video): color, facet, jitter, shadow, shadowColor, shadowOpacity, shadowOffset [dx, dy], seed. The result replaces the source in the stacking order and hides it; applying again replaces; one undo step.
|
|
2356
2404
|
- list_media: every medium with fidelity, definition and limitation. Read it rather than assuming a fixed set — the catalogue grows (hatch and watercolor are recent additions).
|
|
2357
2405
|
- resolve: { medium } → { ok, fidelity, reason, markMaker }. ok:false with a reason is a real answer — pass it on rather than trying anyway.
|
|
2358
2406
|
- list_stitches: the thread stitches with their params.
|
|
@@ -2360,6 +2408,7 @@ ACTIONS:
|
|
|
2360
2408
|
- apply_hatch: rule an item with hatching. Same deal — silhouette is the region, source is hidden, re-hatching replaces.
|
|
2361
2409
|
- list_flow_fields: the 7 fields that bend a hatch line.
|
|
2362
2410
|
- list_hatch_options: the hatch defaults.
|
|
2411
|
+
- sdf_stroke: { points, width? (8), softness? (0.2), color?, opacity?, resolution? (2), name? } → itemId — a smooth, anti-aliased brush stroke along points ({x, y, pressure?} or [x, y, pressure?], canvas px; pressure 0..1 thins the line there), made as an image item (registered, one undo step). Runs on the CPU; a stroke too large for its bitmap budget is refused with the fix. A studio without the SDF brush refuses with the reason.
|
|
2363
2412
|
|
|
2364
2413
|
THE DIRECTION FIELD IS THE WHOLE THING. Stitches that all lie the same way are hatching; stitches that follow the form are needlepainting. Use { kind: 'radial', cx, cy } for anything that radiates (a flower, an eye), { kind: 'spine', spine: [...] } to run them along a midrib or feather shaft, { kind: 'constant', angle } when you actually want flat hatch.
|
|
2365
2414
|
|
|
@@ -2385,9 +2434,14 @@ EXAMPLE — a stitched leaf:
|
|
|
2385
2434
|
inputSchema: {
|
|
2386
2435
|
type: 'object',
|
|
2387
2436
|
properties: {
|
|
2388
|
-
action: { type: 'string', enum: ['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options', 'apply'], description: 'Medium operation' },
|
|
2437
|
+
action: { type: 'string', enum: ['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options', 'apply', 'sdf_stroke'], description: 'Medium operation' },
|
|
2389
2438
|
medium: { type: 'string', enum: [...DESIGN_MEDIA], description: `resolve / apply: the medium. apply works for ${Object.keys(DESIGN_MEDIA_APPLY).join(', ')}; the rest are refused by the engine with the reason. Call list_media for each one's fidelity and limitation.` },
|
|
2390
2439
|
options: { type: 'object', description: 'apply: the medium\'s own options (see the description; color takes hex).' },
|
|
2440
|
+
points: { type: 'array', items: {}, description: 'sdf_stroke: [{x, y, pressure?}] or [[x, y, pressure?]] in canvas px' },
|
|
2441
|
+
softness: { type: 'number', description: 'sdf_stroke: edge softness 0..1 (default 0.2)' },
|
|
2442
|
+
opacity: { type: 'number', description: 'sdf_stroke: 0..1 (default 1)' },
|
|
2443
|
+
resolution: { type: 'number', description: 'sdf_stroke: bitmap px per canvas px (default 2)' },
|
|
2444
|
+
name: { type: 'string', description: 'sdf_stroke: item name' },
|
|
2391
2445
|
itemId: { type: 'string', description: 'apply / apply_thread / apply_hatch: a closed path, compound path (holes kept), or a group of them (applied per path).' },
|
|
2392
2446
|
stitch: { type: 'string', enum: [...THREAD_STITCHES], description: "apply_thread: default longAndShort. These six are the stitches the engine publishes; call 'list_stitches' for each one's description and its own parameters. An unknown name is refused rather than quietly stitched as a default fill." },
|
|
2393
2447
|
field: { type: 'object', description: "apply_thread: { kind: 'radial'|'spine'|'constant', cx, cy, angle, spine[], across } — default radial from the shape centre. spine is a list of points, each {x, y} or [x, y], e.g. [{x:400,y:200},{x:400,y:520}]; this tool normalises [x, y] to {x, y} before the engine sees it." },
|
|
@@ -2429,12 +2483,12 @@ EXAMPLE — a stitched leaf:
|
|
|
2429
2483
|
idempotentHint: false,
|
|
2430
2484
|
openWorldHint: false,
|
|
2431
2485
|
},
|
|
2432
|
-
description: `Animate text CHARACTER BY CHARACTER —
|
|
2486
|
+
description: `Animate text CHARACTER BY CHARACTER — the terminaltexteffects vocabulary, reimplemented from source).
|
|
2433
2487
|
|
|
2434
2488
|
⚠️ THIS REPLACES THE TEXT ITEM. Unlike pinepaper_text_style (which ADOPTS the text's registry id), this EXPLODES the text into one item per character and REMOVES the original. Any relation, keyframe, or handle pointing at the source id DIES with it. Pass keepSource: true to leave the original in place. The returned \`ids\` array is your entire new handle set.
|
|
2435
2489
|
|
|
2436
2490
|
ACTIONS:
|
|
2437
|
-
- list: {} —
|
|
2491
|
+
- list: {} — every effect as {key, label, definition}. Call this first; effect keys come from here.
|
|
2438
2492
|
- apply: { itemId, effect, duration?, seed?, gradient?, gradientStops?, gradientDirection?, gradientSteps?, keepSource?, options? } — explode and animate.
|
|
2439
2493
|
|
|
2440
2494
|
WHAT YOU GET: ordinary keyframe-animated items. It scrubs on the timeline, survives undo and session restore, and exports through the existing MP4 / SMIL / Lottie paths — this is not a frame-callback effect that renders on screen and vanishes from your export. Every effect ends AT REST, so \`duration\` is the settle time and the text is readable afterwards.
|
|
@@ -2583,9 +2637,10 @@ RECIPE — assemble-from-pieces: shatter with keepSource, keyframe each tile fro
|
|
|
2583
2637
|
|
|
2584
2638
|
Input is the output of a single-image layer decomposer (See-through — one flat anime drawing in, ~20 occlusion-inpainted layers out — or any PSD-style layer dump with the same manifest shape). The decomposition model does NOT run here: it needs a GPU and minutes per image; run it where it fits and import the folder.
|
|
2585
2639
|
|
|
2586
|
-
Layer tags
|
|
2640
|
+
Layer tags resolve to the role tokens the expresses presets read: decomposer tags (eyel / eyer, iridesl / iridesr, eyebrowl / eyebrowr, hairf, mouth, face…) and the role tokens themselves (eye_left, pupil_right…) both work, in any case and with _ or - separators, so blink / smile / talk work on the imported character immediately — through an 'expresses' RELATION (pinepaper_add_relation relationType expresses), which is the only thing that drives them. pinepaper_animate applies ambient motion (bounce, pulse, wobble) and cannot blink: asking it to is a silent no-op.
|
|
2587
2641
|
|
|
2588
|
-
Params: { info — the decomposer manifest, whose parts live under info.parts (or info.tag2pinfo) as an OBJECT KEYED BY TAG, not a layers[] array: {
|
|
2642
|
+
Params: { info — the decomposer manifest, whose parts live under info.parts (or info.tag2pinfo) as an OBJECT KEYED BY TAG, not a layers[] array: { eyel: { xyxy: [x0,y0,x1,y1], depth_median: <draw order> }, … } plus frame_size [h,w]. An array here imports nothing and reports no parts. images ({tag: PNG data URL or CORS-reachable https URL} — data URLs are the reliable path; ~20 layers of base64 is a big call, use URLs when you can), position?, scale?, name? }
|
|
2643
|
+
A supplied image that does not land (no manifest entry, no usable xyxy) FAILS the call by name, with skipped: [...] and the groupId of what did land; layers that imported without a role are listed in unroled.
|
|
2589
2644
|
Returns: { groupId, parts, roles, rolesWired, warnings }
|
|
2590
2645
|
|
|
2591
2646
|
CHECK rolesWired: a character importing with 0 roles renders perfectly and silently refuses to animate — that number is the difference between a picture and a puppet.`,
|
|
@@ -2671,7 +2726,7 @@ A partial composition reports as a failure with the elements that did not create
|
|
|
2671
2726
|
title: { type: 'string', description: 'compose: the headline most styles build around.' },
|
|
2672
2727
|
subtitle: { type: 'string', description: 'compose: the supporting line.' },
|
|
2673
2728
|
body: { type: 'string', description: 'compose: body copy, where the style has room for it.' },
|
|
2674
|
-
width: { type: 'number', description: 'compose: canvas width
|
|
2729
|
+
width: { type: 'number', description: 'compose: canvas width. compose SETS the canvas to the size it laid out for. Most styles honour width/height; a few lay out at their own fixed size, and the result warns when the requested size was not used. canvasResized says when the canvas changed.' },
|
|
2675
2730
|
height: { type: 'number', description: 'compose: canvas height.' },
|
|
2676
2731
|
variant: { type: 'string', description: "compose: a style-specific variant, e.g. art deco's 'emerald'." },
|
|
2677
2732
|
draw: { type: 'boolean', description: 'compose: draw it (default true). false returns the scene and ops as data and draws nothing.' },
|
|
@@ -2695,7 +2750,7 @@ A different construction from pinepaper_character, which places a figure from th
|
|
|
2695
2750
|
|
|
2696
2751
|
TO MAKE IT ACT, not just stand: gait is how it walks and is never just walking; poses is the body over time, [{at, pose}]; sequence is a named track of those. Pass gait and travel in the SAME call to cross the frame — they share one track and two calls fight over it. A mistyped gait, pose or sequence comes back NAMED in warnings with the valid list, and the figure is still built.
|
|
2697
2752
|
|
|
2698
|
-
action 'figure' builds the figure. action 'set' builds the floor, the wall and the objects around it, so a figure has somewhere to be.
|
|
2753
|
+
action 'figure' builds the figure. action 'set' builds the floor, the wall and the objects around it, so a figure has somewhere to be. set takes kind: 'room' (floor and wall: groundY?, wall?, floor?), 'tabletop' (object, surfaceY), 'chair' (facing), 'table', 'counter', 'door', 'shelf' (items), 'window'; all take at and scale.
|
|
2699
2754
|
|
|
2700
2755
|
The geometry is vendored from mcp-cloud, which makes this a three-repo artifact: a change to the kit lands there first and is re-vendored into the engine.`,
|
|
2701
2756
|
inputSchema: {
|
|
@@ -2703,7 +2758,7 @@ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact:
|
|
|
2703
2758
|
properties: {
|
|
2704
2759
|
action: { type: 'string', enum: ['figure', 'set'], description: "'figure' or 'set'." },
|
|
2705
2760
|
id: { type: 'string', description: 'Prefix for the created items.' },
|
|
2706
|
-
kind: { type: 'string', description: 'figure: which figure. set:
|
|
2761
|
+
kind: { type: 'string', description: 'figure: which figure. set: room | tabletop | chair | table | counter | door | shelf | window (an unknown one is refused with the current list).' },
|
|
2707
2762
|
at: { type: 'object', description: '{ x?, y? } — where it goes.' },
|
|
2708
2763
|
scale: { type: 'number', description: 'Size multiplier.' },
|
|
2709
2764
|
facing: { type: 'string', enum: ['left', 'right'], description: "set: which way a CHAIR faces. A figure has none and passing it here is refused — a figure faces the way it travels." },
|
|
@@ -2761,7 +2816,9 @@ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact:
|
|
|
2761
2816
|
idempotentHint: false,
|
|
2762
2817
|
openWorldHint: false,
|
|
2763
2818
|
},
|
|
2764
|
-
description: `
|
|
2819
|
+
description: `FORM — nav: 'scenes' is the VIDEO story: one scene per part on a scene chain that cuts in time (export as MP4); each part's length follows its role (hook 2.5 s, stat 3 s, section 3.5 s, quote 3 s, exit 3 s; 0.4 s transitions), and the reply carries the cut times. nav: 'panels' (the default) is the WIDGET story: tabs a viewer clicks through (export_widget_html). A video brief needs 'scenes'.
|
|
2820
|
+
|
|
2821
|
+
A piece of prose becomes a scene.
|
|
2765
2822
|
|
|
2766
2823
|
- distill: reduce an article to its story beats and draw NOTHING. This is the half worth having on its own — a caller can read what the distiller made of the text, edit the beats, and only then assemble. Discovering the reading by looking at a finished scene is the expensive way round.
|
|
2767
2824
|
- from_text: distill and assemble in one call, when the reading does not need checking.
|
|
@@ -2770,6 +2827,7 @@ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact:
|
|
|
2770
2827
|
inputSchema: {
|
|
2771
2828
|
type: 'object',
|
|
2772
2829
|
properties: {
|
|
2830
|
+
nav: { type: 'string', enum: ['panels', 'scenes'], description: "'scenes' = the video form (a scene chain that cuts); 'panels' = the widget form (default)" },
|
|
2773
2831
|
action: { type: 'string', enum: ['distill', 'from_text', 'apply_spec', 'plan_book'], description: 'Which story operation.' },
|
|
2774
2832
|
text: { type: 'string', description: 'distill / from_text: the prose.' },
|
|
2775
2833
|
spec: { type: 'object', description: 'apply_spec: the story spec, usually a distilled one you edited.' },
|
|
@@ -2792,13 +2850,25 @@ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact:
|
|
|
2792
2850
|
|
|
2793
2851
|
pinepaper_agent_export covers the PLATFORM formats: png, svg, mp4, webm, gif, pdf, sized for Instagram or YouTube. These are the interchange ones — a Lottie an app plays, a GLB a 3D tool opens, a BVH a rig imports, a PNG sequence an editor ingests. Import is here too, because a format you can only write is half a bridge.
|
|
2794
2852
|
|
|
2795
|
-
Three of these refuse quietly in the engine: exportGLB with no perspective objects and exportBVH with no rigging system both warn to the console and return nothing, and the production build strips the console. Over MCP there is no console to read, so the precondition is checked BEFORE the call and named — "no perspective objects to export — create one with createObject3D first" rather than an empty result and a guess
|
|
2853
|
+
Three of these refuse quietly in the engine: exportGLB with no perspective objects and exportBVH with no rigging system both warn to the console and return nothing, and the production build strips the console. Over MCP there is no console to read, so the precondition is checked BEFORE the call and named — "no perspective objects to export — create one with createObject3D first" rather than an empty result and a guess.
|
|
2854
|
+
|
|
2855
|
+
EXPORTS ARE FILES. Each export is written to a file in the export directory and the result returns { format, filePath, bytes } instead of the data — the bytes are not inlined. An export that produced nothing is refused by name.
|
|
2856
|
+
|
|
2857
|
+
EXPORT:
|
|
2858
|
+
- export_lottie: { options?: { fps?, duration? (s), includeRelations? } } → a .json Lottie file
|
|
2859
|
+
- export_dotlottie: { options?: { fps?, duration? (s), id?, loop?, autoplay? } } → a .lottie file (the zipped Lottie plus manifest)
|
|
2860
|
+
- export_glb: { } → a .glb file of the PERSPECTIVE 3D objects (createObject3D). Refused when there are none. It does not export world3d meshes, which are a separate renderer.
|
|
2861
|
+
- export_bvh: { skeletonId (required), options?: { fps? } } → a .bvh file of that skeleton's motion. Refused when nothing is rigged.
|
|
2862
|
+
- export_png_sequence: { options?: { duration (s), fps, width?, height?, transparent? } } → a .zip of one PNG per frame. PASS duration and fps: the studio does not size the sequence to the scene, and the result's defaultsUsed says when its own default length was used.
|
|
2863
|
+
|
|
2864
|
+
IMPORT:
|
|
2865
|
+
- import_lottie: { data (the Lottie JSON — an object or a JSON string), options?: { fitToCanvas?, autoPlay?, centerOnCanvas?, scale? } } → { itemId, itemIds, items, duration, bounds }; a failed import fails with the importer's errors. data is parsed as JSON, so a URL or a file path does not work; read the file and pass its contents. There is no .lottie, GLB or BVH import here: a glTF goes into a 3D world with the world3d tool's import_gltf, a BVH onto a rig with the rigging tool's import_bvh / retarget_bvh.`,
|
|
2796
2866
|
inputSchema: {
|
|
2797
2867
|
type: 'object',
|
|
2798
2868
|
properties: {
|
|
2799
2869
|
action: { type: 'string', enum: ['export_lottie', 'export_dotlottie', 'import_lottie', 'export_glb', 'export_bvh', 'export_png_sequence'], description: 'Which interchange operation.' },
|
|
2800
2870
|
options: { type: 'object', description: "Format options passed through to the exporter. export_png_sequence: { duration (seconds), fps, width, height, transparent (keep alpha; default false fills the background) } — PASS duration and fps: the studio does not size the sequence to the scene, and without them it writes its own default length, which does not follow the scene (runs have measured 45 and 90 frames). The result's defaultsUsed says when that happened." },
|
|
2801
|
-
data: { anyOf: [{ type: 'string' }, { type: 'object' }], description: 'import_lottie: the Lottie JSON — object
|
|
2871
|
+
data: { anyOf: [{ type: 'string' }, { type: 'object' }], description: 'import_lottie: the Lottie JSON — object or JSON string (a URL or path is not fetched).' },
|
|
2802
2872
|
skeletonId: { type: 'string', description: "export_bvh: which skeleton to write. A BVH is one skeleton's motion." },
|
|
2803
2873
|
},
|
|
2804
2874
|
required: ['action'],
|
|
@@ -2832,6 +2902,36 @@ THE CATALOGUE IS A STARTING SET, NOT THE LIMIT. Six instruments, five drums and
|
|
|
2832
2902
|
- For a drum, pass pitch: { from, tau }. The glide is what makes a kick a kick; without it you get a beep.
|
|
2833
2903
|
- Names are normalised to [a-z][a-z0-9_-]. Register 'Rhodes' and the response returns canonicalName 'rhodes' — play THAT.
|
|
2834
2904
|
|
|
2905
|
+
ARGUMENTS BY ACTION. A sound spec is { note? ('A4') | hz? | chord? + root?, instrument? (a catalogue name), waveform? ('sine' | 'square' | 'sawtooth' | 'triangle'), partials? ([{ h, amp }]), envelope? ({ attack, decay, sustain, release }), gain? (0..1), pan? (-1..1), duration? (seconds) }.
|
|
2906
|
+
|
|
2907
|
+
CATALOGUES:
|
|
2908
|
+
- list_instruments / list_percussion / list_sfx: {} → the engine's names, built-in and defined.
|
|
2909
|
+
|
|
2910
|
+
PLAY — each returns { played }. When the studio returns no voice (it may have no audio output) the call fails and says so; placed sounds and render_soundtrack need no audio output:
|
|
2911
|
+
- play_tone: { note ('A4', required), options? } — options is merged into the spec, so { duration, gain, pan, instrument, envelope, waveform } all work, and options.hz overrides the note. A top-level hz is NOT play_tone's: it belongs to define_percussion / define_sfx, and play_tone without note is refused.
|
|
2912
|
+
- play_chord: { root ('C4'), chord? ('major' default; 'minor', 'maj7', 'dim' …), options? }
|
|
2913
|
+
- play_percussion / play_sfx: { name (from the catalogue), options? }
|
|
2914
|
+
- play_spec: { spec, options? } — options is merged into the spec.
|
|
2915
|
+
- play_from_text: { text, options? } — options is merged into the resolved spec.
|
|
2916
|
+
|
|
2917
|
+
READ WITHOUT PLAYING:
|
|
2918
|
+
- chord_frequencies: { root, chord?, options? } → { frequencies } (Hz)
|
|
2919
|
+
- from_text: { text ('a soft warm bell on A4') } → { spec } to inspect or edit, then play_spec or create
|
|
2920
|
+
- timbre_from_path: { itemId (a path with length), samples? (256) } → { timbre: { partials } }
|
|
2921
|
+
|
|
2922
|
+
ON THE CANVAS — a placed sound is a timeline item:
|
|
2923
|
+
- create: { spec?, visual?: { width? (220), height? (64), position? ({ x, y }), color?, startTime? (s), duration? (s) } } → { itemId, placement? } — startTime / duration place it in the same call.
|
|
2924
|
+
- sequence: { cues: [{ t (s, required), spec? | preset? (an instrument name) + note?, duration?, visual? }] } → { placed, itemIds, cues, failed? } — a whole bed in ONE call; failures come back by index. preset is shorthand for spec: { instrument: preset, note }.
|
|
2925
|
+
- set_placement: { itemId, placement: { startTime?, duration? } } (seconds; no other keys are read) → { placed }
|
|
2926
|
+
- remove: { itemId } → { removed } · stop_all: {} → { stopped } — stops everything sounding now; placed sounds stay placed.
|
|
2927
|
+
|
|
2928
|
+
DEFINE (name is normalised to [a-z][a-z0-9_-]; play the returned canonicalName):
|
|
2929
|
+
- define_instrument: { name, partials (required, ≥1), envelope?, gain? (0.8), aliases? } → { canonicalName, requestedName, normalised, listed }
|
|
2930
|
+
- define_percussion / define_sfx: { name, partials? or noise? (0..1; one of the two is required), noiseFreq? (Hz), hz? (base Hz), pitch? ({ from (Hz), tau (s, ≤5) }), envelope?, gain?, aliases? }
|
|
2931
|
+
|
|
2932
|
+
RENDER:
|
|
2933
|
+
- render_soundtrack: { duration? (s, ≤600), sampleRate? (48000), bitDepth? (16 | 32) } → a WAV FILE; the result names filePath, fileSize, placed and any dropped. Refused by name when no sound is placed.
|
|
2934
|
+
|
|
2835
2935
|
render_soundtrack mixes every placed sound to a WAV and writes it to a file. It runs offline, with no Web Audio and no playback, so it works in a headless studio. An empty scene is refused by name rather than producing a silent file, and any sound that could not be mixed is reported as dropped — a soundtrack missing a track otherwise looks exactly like a complete one.`,
|
|
2836
2936
|
inputSchema: {
|
|
2837
2937
|
type: 'object',
|
|
@@ -2876,7 +2976,7 @@ render_soundtrack mixes every placed sound to a WAV and writes it to a file. It
|
|
|
2876
2976
|
description: `The generators' Animation knob, pointed at anything — the same motion engine every generator's own animation runs on, reachable for any group or list of items.
|
|
2877
2977
|
|
|
2878
2978
|
Two kinds, and the second is the one nothing else here can do:
|
|
2879
|
-
- GROUP motions (
|
|
2979
|
+
- GROUP motions (driftX, driftY, swayX, swayY, rotate, pulse, wave, bounce) move the target as one.
|
|
2880
2980
|
- FIELD motions (ripple, breathe, undulate) sweep a crest THROUGH the children from an origin, with a chosen waveform — each element moves at its own moment in the cycle. That is a wave passing through a crowd, not a crowd moving together.
|
|
2881
2981
|
|
|
2882
2982
|
Call action 'list' first. It returns the engine's own catalogue — every motion, waveform and origin it will accept — so the names come from the engine rather than from an enum here that can drift away from it.
|
|
@@ -2957,9 +3057,49 @@ ACTIONS:
|
|
|
2957
3057
|
- add_actor: { actorId?, x, z, height?, sprite? (a canvas item id), live? } → { actorId }. live: true re-rasterizes the item as it animates — a RIGGED character (walk cycle, expressions) PERFORMS in the world instead of standing there as a photograph of itself.
|
|
2958
3058
|
- remove_actor / list_actors / set_actor_pose: { actorId, pose: { x?, z?, angle? } } — the setter a timeline, sequencer or agent drives frame by frame.
|
|
2959
3059
|
- set_camera: { camera: { mode: 'follow' | 'fixed' | 'orbit', target? (actorId), radius?, speed?, eye?, lookAt? } }
|
|
2960
|
-
- add_object: { object: { x, z, height?, color
|
|
3060
|
+
- add_object: { object: { x, z, height?, color? } } (y defaults to sitting ON the terrain) / remove_object: { objectId }
|
|
2961
3061
|
- remove_world: {} — tears the world down; the Paper canvas was always its own layer and is untouched.
|
|
2962
3062
|
|
|
3063
|
+
UNITS: positions, sizes, ranges and radii are WORLD units (metres). rotY is RADIANS everywhere on the mesh path (pass mesh.rotYDegrees to write degrees); lathe arc is DEGREES. Colours take hex or [r, g, b] in 0..1. These act on an existing world: call create first.
|
|
3064
|
+
|
|
3065
|
+
MESHES — a canvas path becomes real geometry:
|
|
3066
|
+
- extrude_path: { pathId, mesh?: { id?, depth? (1), unitsPerPixel? (0.01: canvas px → world units), upright? (stand the drawing up; default flat, a floor plan), placement? ('centered' default: centred on x/z, base at y | 'canvas'), caps?, segments?, flatness?, x?, y?, z?, rotY?, rotYDegrees?, scale?, doubleSided?, color?, metalness?, roughness?, uniforms? } } → { meshId }. The path is unchanged.
|
|
3067
|
+
- lathe_path: { pathId, mesh?: same fields, plus arc? (DEGREES, 360 = full solid); segments? = steps round the revolution } → { meshId }
|
|
3068
|
+
- list_meshes: {} → { count, meshes } (long vertex arrays summarised to their length)
|
|
3069
|
+
- remove_mesh: { meshId }
|
|
3070
|
+
- set_mesh_instances: { meshId, instances: [[x, y, z, scale, rotY (RADIANS), variant], …] } — replaces the copies a mesh draws, six numbers each.
|
|
3071
|
+
|
|
3072
|
+
LIGHTS — point lights, at most 8 (the ninth is refused by name):
|
|
3073
|
+
- add_light: { light: { id?, x? (0), y? (3), z? (0), color? (hex or linear [r, g, b], may exceed 1), intensity? (6), range? (20) } } → { lightId, light }
|
|
3074
|
+
- set_light: { lightId, light: any of the add_light fields } — moves or retunes in place.
|
|
3075
|
+
- remove_light: { lightId } · list_lights: {} → { lights }
|
|
3076
|
+
|
|
3077
|
+
MATERIALS — a NAMED, SHARED surface: one edit restyles every object using it.
|
|
3078
|
+
- add_material: { material: { id?, color?, emissive?, emissiveIntensity?, metalness? (0..1), roughness? (0..1), clearcoat?, clearcoatRoughness?, sheenColor?, sheenRoughness? } } → { materialId, material }. metalness / roughness reach pixels on the MESH path only.
|
|
3079
|
+
- set_material: { materialId, material: any of the add_material fields } · remove_material: { materialId } · list_materials: {} → { materials }
|
|
3080
|
+
|
|
3081
|
+
IMPORT:
|
|
3082
|
+
- import_obj: { source (the .obj file TEXT), importOptions?: { id?, x?, y?, z?, rotY? (RADIANS), scale?, doubleSided?, uniforms?, color? ([r, g, b] 0..1; .mtl is never read) } } → { meshIds, warnings }
|
|
3083
|
+
- import_gltf: { source (the .gltf JSON TEXT, buffers embedded), importOptions?: { id?, x?, y?, z?, rotY?, scale?, doubleSided?, uniforms?, merge? (default true) } } → { meshIds, warnings }. A URL, a data: URI or a file path is NOT fetched — the string is parsed as glTF JSON. Unsupported features come back named in warnings.
|
|
3084
|
+
- list_mesh_clips: { meshId } → { clips } (the animation clips a skinned import carries)
|
|
3085
|
+
- set_mesh_clip: { meshId, clip, crossfade? (seconds) }
|
|
3086
|
+
|
|
3087
|
+
NAVIGATION, PICKING, CAMERA:
|
|
3088
|
+
- set_nav_target: { navTarget: '2d' | '3d' } — which layer the pointer drives · get_nav_target: {} → { navTarget }
|
|
3089
|
+
- ground_height: { point: { x, z } (WORLD) } → { height } of the terrain there
|
|
3090
|
+
- raycast: { origin: [x, y, z], direction: [x, y, z] (need not be normalised) } → { hit (registry id, point …) | null, missed }
|
|
3091
|
+
- line_of_sight: { from: [x, y, z], to: [x, y, z] } → { clear }
|
|
3092
|
+
- world_to_canvas: { point: { x, y, z? } (WORLD) } → { point: { x, y } } in canvas px; refused when the point is behind the camera
|
|
3093
|
+
- canvas_to_ground: { point: { x, y } (CANVAS px) } → { ground } where that pixel lands on the terrain
|
|
3094
|
+
- dolly_camera: { multiplier } (>1 away, <1 closer) → { camera }
|
|
3095
|
+
- pan_camera: { dx, dy } (SCREEN px) → { camera }
|
|
3096
|
+
|
|
3097
|
+
PHYSICS — narrow by design: spheres and capsules under gravity colliding with terrain, objects, actors and meshes. No stacking, no joints (pinepaper_physics is the 2D world and has those).
|
|
3098
|
+
- add_body: { body: { id?, kind? ('sphere' default | 'capsule'), position? ([x, y, z]), velocity? ([x, y, z]), radius? (0.5), height? (1.8, capsule), mass? (1), restitution? (0.3), friction? (0.6), gravityScale? (1), kinematic?, ignoreIds?, ttl? (seconds), data? } } → { bodyId, body }
|
|
3099
|
+
- fire_projectile: { body: { id?, position?, direction? ([0, 0, -1]), speed? (40), radius? (0.15), restitution? (0), gravityScale? (0.35), ttl? (6 s), ignoreIds? (e.g. the shooter), data? } } → { id, body } — a SWEPT sphere, so it cannot tunnel through a thin wall.
|
|
3100
|
+
- impulse: { bodyId, vector: [x, y, z] } · set_velocity: { bodyId, vector: [x, y, z] }
|
|
3101
|
+
- step_physics: { dt (seconds) } → { contacts, removed } · remove_body: { bodyId } · list_bodies: {} → { bodies }
|
|
3102
|
+
|
|
2963
3103
|
RECIPE — a character walking through a forest: create {spec:'forest'} → import/rig a character on the canvas → add_actor {sprite: itemId, live: true} → set_camera {mode:'follow', target} → drive set_actor_pose over time (or let the built-in character control walk).`,
|
|
2964
3104
|
inputSchema: {
|
|
2965
3105
|
type: 'object',
|
|
@@ -2990,7 +3130,7 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2990
3130
|
lightId: { type: 'string', description: 'set_light / remove_light: the light id.' },
|
|
2991
3131
|
material: { type: 'object', description: 'add_material / set_material: colours (color, emissive, sheenColor) take hex or [r, g, b] in 0..1. { id?, color?, emissive?, metalness?, roughness?, emissiveIntensity?, clearcoat?, clearcoatRoughness?, sheenColor?, sheenRoughness? }. A shared surface — one edit restyles every object using it. metalness/roughness render on the mesh path only.' },
|
|
2992
3132
|
materialId: { type: 'string', description: 'set_material / remove_material: the material id.' },
|
|
2993
|
-
source: { type: 'string', description: 'import_obj: the OBJ file TEXT. import_gltf:
|
|
3133
|
+
source: { type: 'string', description: 'import_obj: the OBJ file TEXT. import_gltf: the .gltf JSON TEXT, buffers embedded (a URL, data: URI or path is not fetched; .glb is not supported here).' },
|
|
2994
3134
|
importOptions: { type: 'object', description: 'import_obj / import_gltf: placement and scale options, passed through.' },
|
|
2995
3135
|
clip: { type: 'string', description: 'set_mesh_clip: which imported animation clip a skinned mesh plays.' },
|
|
2996
3136
|
crossfade: { type: 'number', description: 'set_mesh_clip: seconds to blend out of the previous clip (blended in LOCAL pose space).' },
|
|
@@ -3031,7 +3171,7 @@ The knowledge of how a figure is built and how it moves lives in the graph; this
|
|
|
3031
3171
|
{ at: 1.8, channel: "say", until: 4.2 },
|
|
3032
3172
|
{ at: 5.0, channel: "headTurn", value: -0.8 } ] }
|
|
3033
3173
|
|
|
3034
|
-
CHANNELS ARE DECLARED BY THE CONCEPT, not by you. Ask
|
|
3174
|
+
CHANNELS ARE DECLARED BY THE CONCEPT, not by you. Ask pinepaper_query_ontology for a concept to see the ones it has. Four kinds exist:
|
|
3035
3175
|
- transform: one part deformed with an attack, a hold and a release — a blink is the eyes flattening in scaleY and easing back. This is what gives a gesture a MIDDLE; a swap between two drawings has only two ends and reads as flashing.
|
|
3036
3176
|
- cycle: a repeating throw that runs for as long as you ask — a pigeon's head bob, a tail flick. Amplitude is a fraction of the part, so it means the same at any size.
|
|
3037
3177
|
- shift: every part moved together, for a turn a flat drawing cannot rotate into.
|
|
@@ -3119,7 +3259,7 @@ POSE MOTION — build a performance out of saved poses:
|
|
|
3119
3259
|
· plan: true returns the seams without touching the skeleton, so you can check the joins first.
|
|
3120
3260
|
- move_root: a deterministic locomotion track, keyed in SCENE SECONDS on the same clock as the pose sequence. Without it a walk cycle is a march on the spot; with a last keyframe shorter than the performance, the figure arrives early and stands there.
|
|
3121
3261
|
- auto_walk / auto_breath / auto_idle / auto_jump: one-call procedural layers. breath and idle find their bones BY NAME (spine/chest/body, head/hip/spine) and refuse with that list if the rig uses other names.
|
|
3122
|
-
- bake_animation: freeze the whole rig into plain item keyframes for export. Needs items ATTACHED to bones
|
|
3262
|
+
- bake_animation: { skeletonId, options?: { duration? (2), fps? (30), detach? (true), write? (true) } } → { written, keyframes, detached, wrote } — freeze the whole rig into plain item keyframes for export. Needs items ATTACHED to bones. It DETACHES the items (the bones stop driving them, so the keyframes are what plays); pass options.detach:false to keep the rig driving.
|
|
3123
3263
|
- add_secondary_motion: spring chains for tails, hair, cloth. skin_path: per-vertex deformation of a curved path.
|
|
3124
3264
|
|
|
3125
3265
|
ACTIONS:
|
|
@@ -3134,6 +3274,12 @@ ACTIONS:
|
|
|
3134
3274
|
- import_bvh: { bvhText, view? (side|front), fps? (15; 0 = every frame), height? (320px), rootPosition?, name? } → { skeletonId, poses, duration, warnings } — build a NEW rig from a BVH mocap clip (CMU / Mixamo), stick figure included. Root translation returns as a separate track rather than baked into poses.
|
|
3135
3275
|
- retarget_bvh: { bvhText, skeletonId, fps?, name? } → { skeletonId, matched, unmatchedSource, unmatchedTarget, poses, duration } — drive an EXISTING rig with a BVH clip, bones matched by name. CHECK matched/unmatched in the result: a retarget that matched 2 of 15 bones "succeeds" and looks broken.
|
|
3136
3276
|
- import_spine: { spineJson, rootPosition?, name? } → { skeletonId, bones, placeholders, animations, warnings } — import a Spine editor JSON export (bones, poses, attachment placeholders).
|
|
3277
|
+
- list_skeletons: {} → { skeletons: [{ id, name, boneCount, rootBoneId, hasIK, hasPhysics }], count } — needs no skeletonId; the place to find one on a rig you did not build.
|
|
3278
|
+
- stop_pose_sequence: { skeletonId } — stops the playing pose sequence; joints go back to their poses / rest. Refused (a no-op, not a fault) when nothing was playing.
|
|
3279
|
+
- apply_pose_transition: { skeletonId, transitionName, poseIdMap? ({ transitionPoseName: savedPoseId }) } — plays a named stock transition (walk, run, flap …; list_pose_libraries returns the names). It plays poses BY NAME, so load_pose_library first; poseIdMap is built from list_poses by name when omitted.
|
|
3280
|
+
- stop_root_track: { skeletonId } — stops a move_root locomotion track; the figure stays where it is. Refused (a no-op) when no track was running.
|
|
3281
|
+
- list_shape_keys: { skeletonId } → { shapeKeys: [{ id, name }], count }
|
|
3282
|
+
- load_shape_key: { skeletonId, shapeKeyId, weight? (0..1, default 1) } — blends the attached items toward a shape key saved with save_shape_key.
|
|
3137
3283
|
|
|
3138
3284
|
BREAKDOWN POSES (S12): a breakdown keyframe shapes the ARC + SPACING between key poses (favor biases spacing; boneOffsets lag bones so a tip drags its root — follow-through; movingHold keeps a hold alive). This is the difference between mechanical and lifelike motion.`,
|
|
3139
3285
|
inputSchema: {
|
|
@@ -3234,7 +3380,7 @@ EXAMPLE doc: { "nodes": [ {"id":"bar","type":"pp:Rectangle","width":300,"height"
|
|
|
3234
3380
|
width: { type: 'number' },
|
|
3235
3381
|
height: { type: 'number' },
|
|
3236
3382
|
},
|
|
3237
|
-
description: 'Canvas size
|
|
3383
|
+
description: 'Canvas size to lay out for (default: the live canvas). The result returns registry itemIds, the document ids as docIds, and idMap pairing them.',
|
|
3238
3384
|
},
|
|
3239
3385
|
defaultGeometry: {
|
|
3240
3386
|
type: 'object',
|
|
@@ -3342,6 +3488,7 @@ An event channel (pinepaper_event) is the hub: something FIRES it, and reactions
|
|
|
3342
3488
|
· on_event_fire_after (params: delay, timeline) — chain one beat to the next.
|
|
3343
3489
|
· on_event_fire_if — the only reaction that TESTS graph state rather than mutating it; params.predicate is an ExpressionIR program.
|
|
3344
3490
|
· on_event_add_relation / on_event_remove_relation — the scene rewires itself.
|
|
3491
|
+
· on_event_play_sound (source = the EVENT, target null; params: sound, gain?, note?) — play a sound when the event fires: a catalogue name (effect, drum or instrument; aliases accepted) or a define_sfx spec; gain is a multiplier capped at full scale; note is for instruments (default C5). An unknown sound is refused with the catalogue. Wire a click sound as on_click_fire (button → event) + on_event_play_sound (event → null). For a sound at a TIME, pinepaper_event create { at: seconds | [seconds] } fires the event on the timeline (and in video exports); a scene chain's cutSound puts one on every cut. LIMITS: the exported widget player does not run timed triggers, and a browser may block sound until the viewer's first click or key.
|
|
3345
3492
|
· on_event_store_set / on_event_store_increment / restores_from — persistent state. The target is a store node whose id is the storage SCOPE, so two stores in a scene and two widgets on a page do not collide. restores_from is the read half and fires ONCE at load.
|
|
3346
3493
|
- Proximity (pointer over an item): on_enter_* / on_exit_* mirror the on_event_* reactions above (set_color, set_property, set_property_from_template, set_visibility, set_data, increment, toggle) — hover states without a single line of script.
|
|
3347
3494
|
- Exclusive state: exclusive_group makes at most one member active (on_event_set_active is how you activate one — tabs, steppers, quiz cards). menubar_group is the same grouping WITHOUT the mutex.
|
|
@@ -3411,8 +3558,8 @@ Relations are COMPOSITIONAL - an item can have multiple relations that work toge
|
|
|
3411
3558
|
},
|
|
3412
3559
|
relationType: {
|
|
3413
3560
|
type: 'string',
|
|
3414
|
-
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
3415
|
-
description: 'Type of relationship',
|
|
3561
|
+
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_event_play_sound', 'looks_at', 'reacts_to', 'drag_to_turn', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
3562
|
+
description: 'Type of relationship. A name registered with pinepaper_register_custom_relation in this session is accepted too.',
|
|
3416
3563
|
},
|
|
3417
3564
|
params: {
|
|
3418
3565
|
type: 'object',
|
|
@@ -3454,7 +3601,7 @@ USE WHEN:
|
|
|
3454
3601
|
targetId: { type: 'string', description: 'Target item ID' },
|
|
3455
3602
|
relationType: {
|
|
3456
3603
|
type: 'string',
|
|
3457
|
-
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
3604
|
+
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_event_play_sound', 'looks_at', 'reacts_to', 'drag_to_turn', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
3458
3605
|
description: 'Specific relation type to remove (optional - removes all if not specified)',
|
|
3459
3606
|
},
|
|
3460
3607
|
},
|
|
@@ -3482,7 +3629,7 @@ USE WHEN:
|
|
|
3482
3629
|
itemId: { type: 'string', description: 'Item to query relations for' },
|
|
3483
3630
|
relationType: {
|
|
3484
3631
|
type: 'string',
|
|
3485
|
-
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
3632
|
+
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_event_play_sound', 'looks_at', 'reacts_to', 'drag_to_turn', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
3486
3633
|
description: 'Filter by relation type (optional)',
|
|
3487
3634
|
},
|
|
3488
3635
|
direction: {
|
|
@@ -3503,7 +3650,9 @@ USE WHEN:
|
|
|
3503
3650
|
idempotentHint: false,
|
|
3504
3651
|
openWorldHint: false,
|
|
3505
3652
|
},
|
|
3506
|
-
description: `
|
|
3653
|
+
description: `PARAMS ARE DEFINITIONS, not values: { gap: { type: 'number', default: 60, min: 0 } } (type is number | string | boolean | array | object). Once registered, the name is a valid relationType for pinepaper_add_relation in this session.
|
|
3654
|
+
|
|
3655
|
+
Register a custom relation type with compute and apply functions.
|
|
3507
3656
|
|
|
3508
3657
|
USE WHEN:
|
|
3509
3658
|
- Creating physics-based behaviors (attraction, repulsion, collision)
|
|
@@ -3744,6 +3893,10 @@ EASING — IT SHAPES THE SEGMENT ARRIVING AT THE KEYFRAME IT IS ON.
|
|
|
3744
3893
|
The move from keyframe A to keyframe B is eased by B's easing, not A's. Easing on the FIRST keyframe does nothing (no segment arrives at it) — a Ken Burns with easing only on its first key plays linear. Put the easing on the key you are moving TO.
|
|
3745
3894
|
${EASING_LINES}
|
|
3746
3895
|
- or a custom cubic-bezier [x1, y1, x2, y2] — x in 0..1, y may exceed 0..1 for overshoot. keyframe_animate and camera_animate only.
|
|
3896
|
+
COUNTERS: a text item with a counter (create_item / modify_item counter) also animates the numeric property counterValue, drawn through the counter's format — keyframe it to shape a count by hand (hold on a number, then race). Its keys override the counter's own from / to.
|
|
3897
|
+
|
|
3898
|
+
HOLD KEYS (where the studio has them): interpolation: 'hold' on a key holds ITS value until the next key, then cuts — no in-between. Easing 'hold' / 'step' on the ARRIVING key does the same. A studio without hold keys refuses rather than interpolate; there, two keys a frame apart make the cut. For a whole layer 'on twos', set stepTiming on the item (create_item / modify_item).
|
|
3899
|
+
|
|
3747
3900
|
Every easing renders exactly in video, GIF and APNG. Lottie and CSS get the closest single Bézier: springPlayful loses its wobble, and the in-out quart/quint/expo/circ curves are within 2–4 %. SMIL is exact for springs and those four.
|
|
3748
3901
|
|
|
3749
3902
|
CLIP WINDOW (optional — Premiere/Canva-style clip behavior):
|
|
@@ -3824,6 +3977,8 @@ USE WHEN:
|
|
|
3824
3977
|
- Seeking to specific time
|
|
3825
3978
|
- Controlling animation state
|
|
3826
3979
|
|
|
3980
|
+
PLAYBACK: play { duration?, loop? } · pause {} (holds the playhead) · stop {} (stops and rewinds) · seek { time (s), deterministic? }
|
|
3981
|
+
|
|
3827
3982
|
DETERMINISTIC SEEK: pass deterministic: true with action 'seek' to evaluate the WHOLE scene at the exact time via app.sceneAt(t) — keyframes + relations + generators all tick to t, so the same time always yields the same frame (paired with a seed in pinepaper_capture_frames for reproducible output). Plain seek only sets the keyframe playback state. Use deterministic seek before pinepaper_browser_screenshot to capture a reproducible frame.
|
|
3828
3983
|
|
|
3829
3984
|
RATE (set_time_scale / get_time_scale): 1 is real time, 0.5 half speed, 0 freezes the clock WITHOUT stopping playback, and a negative rate runs the scene backwards. Changing it preserves the current position — the clock is rebased, so the playhead does not jump. Export is deliberately unaffected: a scene watched at 0.5x still exports its real duration rather than a file twice as long.
|
|
@@ -4402,7 +4557,7 @@ SUPPORTED ANIMATIONS: bounce, breathe, fade, glow, jelly, path, pulse, rotate, s
|
|
|
4402
4557
|
target: { type: 'string', description: 'Name of target item' },
|
|
4403
4558
|
type: {
|
|
4404
4559
|
type: 'string',
|
|
4405
|
-
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
4560
|
+
enum: ['orbits', 'follows', 'attached_to', 'maintains_distance', 'points_at', 'mirrors', 'parallax', 'bounds_to', 'anchored_in_world', 'spring_follow', 'repels', 'attracts', 'wiggle', 'animates', 'grows_from', 'staggered_with', 'indicates', 'circumscribes', 'wave_through', 'morphs_to', 'group_morphs_to', 'moves_along_path', 'construction_reveal', 'triggers_animation', 'syncs_with', 'tours', 'synced_to_audio', 'expresses', 'camera_follows', 'camera_animates', 'is_midpoint_of', 'lies_on_line', 'is_centroid_of', 'is_circumcenter_of', 'concentric_with', 'driven_by', 'time_expression', 'on_click_fire', 'on_pointer_enter_fire', 'on_pointer_exit_fire', 'on_key_fire', 'on_event_fire_after', 'on_event_fire_if', 'on_event_add_relation', 'on_event_remove_relation', 'on_event_set_color', 'on_event_set_property', 'on_event_set_property_from_template', 'on_event_set_visibility', 'on_event_set_active', 'on_event_set_data', 'on_event_increment', 'on_event_toggle', 'on_event_store_set', 'on_event_store_increment', 'on_event_play_sound', 'looks_at', 'reacts_to', 'drag_to_turn', 'on_enter_set_color', 'on_enter_set_property', 'on_enter_set_property_from_template', 'on_enter_set_visibility', 'on_enter_set_data', 'on_enter_increment', 'on_enter_toggle', 'on_exit_set_color', 'on_exit_set_property', 'on_exit_set_property_from_template', 'on_exit_set_visibility', 'on_exit_set_data', 'on_exit_increment', 'on_exit_toggle', 'exclusive_group', 'menubar_group', 'restores_from', 'on_top_of', 'below', 'beside', 'inside', 'centered_on', 'aligned_with', 'part_of', 'connects_to', 'attached_to_tail', 'head_points_to'],
|
|
4406
4561
|
},
|
|
4407
4562
|
params: {
|
|
4408
4563
|
type: 'object',
|
|
@@ -4535,6 +4690,7 @@ EXAMPLE — Slideshow:
|
|
|
4535
4690
|
inputSchema: {
|
|
4536
4691
|
type: 'object',
|
|
4537
4692
|
properties: {
|
|
4693
|
+
cutSound: { description: "create_chain: a sound at every cut — a catalogue name, or {sound, gain?, offset? (s)}. The scene_cut event also fires at each cut.", oneOf: [{ type: 'string' }, { type: 'object', additionalProperties: true }] },
|
|
4538
4694
|
action: {
|
|
4539
4695
|
type: 'string',
|
|
4540
4696
|
enum: ['create_chain', 'play', 'pause', 'resume', 'stop', 'toggle_loop', 'jump'],
|
|
@@ -5050,7 +5206,7 @@ flags as a badly-composed scene, and they do not survive an artboard change.`,
|
|
|
5050
5206
|
medium: { type: 'string', enum: ['vector', 'thread'], description: "What makes the marks. 'thread' stitches every closed path AFTER arranging; photographs and text cannot be stitched and return in medium.skipped with the reason. Other media are refused with their own reason rather than faked." },
|
|
5051
5207
|
stitch: { type: 'string', enum: [...THREAD_STITCHES], description: 'Which stitch when medium is thread (default longAndShort) — the same six pinepaper_design_medium offers.' },
|
|
5052
5208
|
stitchBudget: { type: 'number', description: 'Total marks across the composition (default 6000); the stitch scales to fit rather than the fill being cut short.' },
|
|
5053
|
-
action: { type: 'string', enum: ['list_patterns', 'list_treatments', 'apply', 'set_treatment'] },
|
|
5209
|
+
action: { type: 'string', enum: ['list_patterns', 'list_treatments', 'list_reveals', 'list_styles', 'apply', 'set_treatment'] },
|
|
5054
5210
|
pattern: { type: 'string', description: 'Pattern key from list_patterns' },
|
|
5055
5211
|
itemIds: { type: 'array', items: { type: 'string' }, description: 'Items in slot order' },
|
|
5056
5212
|
treatment: { type: 'string', description: 'Camera treatment key' },
|
|
@@ -5058,6 +5214,15 @@ flags as a badly-composed scene, and they do not survive an artboard change.`,
|
|
|
5058
5214
|
applyCamera: { type: 'boolean', description: 'false to arrange without compiling a camera track' },
|
|
5059
5215
|
loop: { type: 'boolean' },
|
|
5060
5216
|
craft: { type: 'object', description: 'Override craft ratios (gutter, margin…), as fractions of the short canvas edge' },
|
|
5217
|
+
// These were in the Zod schema and the description but not here, so a
|
|
5218
|
+
// client never saw them (1.6.19 gate C3).
|
|
5219
|
+
reveal: { type: ['string', 'null'], description: 'apply: temporal reveal key from list_reveals — items arriving over time' },
|
|
5220
|
+
revealOptions: { type: 'object', description: 'apply: reveal tuning { duration, beat, fade, beats: number[] }; explicit beats sync cuts to audio' },
|
|
5221
|
+
style: { type: 'string', description: 'apply: visual style bundle from list_styles (treatments + spacing + motion defaults)' },
|
|
5222
|
+
text: { type: 'array', items: { type: 'string' }, description: "apply: content for the pattern's TEXT slots (headline, caption…), in slot order" },
|
|
5223
|
+
assets: { type: 'array', items: { type: 'string' }, description: "apply: SVG markup for the pattern's VECTOR slots (badge, logo mark), in slot order" },
|
|
5224
|
+
audio: { type: 'string', description: 'apply: audio source to beat-sync the reveal to' },
|
|
5225
|
+
grid: { type: 'boolean', description: 'apply with audio: snap to an even tempo grid instead of raw onsets' },
|
|
5061
5226
|
},
|
|
5062
5227
|
required: ['action'],
|
|
5063
5228
|
},
|
|
@@ -5074,6 +5239,8 @@ ACTIONS:
|
|
|
5074
5239
|
- plan: Report what WOULD change (count, per-role breakdown, WCAG contrast audit) WITHOUT touching
|
|
5075
5240
|
the canvas. Call this first on an unfamiliar scene; a whole-scene recolour is hard to eyeball afterwards.
|
|
5076
5241
|
- apply: Commit it. A history snapshot is taken FIRST, so one undo puts everything back.
|
|
5242
|
+
- set: { kit } — apply it AND make it the ACTIVE brand for the rest of the piece: every shape created afterwards (create_item, batch create, agent_batch_execute) takes colors.primary, and every text colors.text with the heading font from 32 px up (body below), wherever the call names none. Pass noBrand: true in an item's properties to opt it out. Use set at the start of a multi-beat piece so beat 5 matches beat 1. It lives with the studio session: set it again after a reload.
|
|
5243
|
+
- get: the active brand (or null). clear: stop inheriting (what is on the canvas keeps its colours).
|
|
5077
5244
|
- from_url: { url, screenshots? } — read the product's REAL brand from its own site before animating anything: screenshots down the page, the logo (header image or SVG, else the site icon), and a proposed kit from the colours and fonts the site's CSS computes (buttons and links weigh most). Saved to files; the result lists them. Read in an isolated browser context: the studio is not touched. Review the kit against the screenshots, then plan / apply it. Use the brand you are given, never an invented one.
|
|
5078
5245
|
|
|
5079
5246
|
KIT SHAPE — only name and colors.primary are required:
|
|
@@ -5089,7 +5256,7 @@ Both actions return a WCAG contrast audit, so you can see failing pairs before s
|
|
|
5089
5256
|
inputSchema: {
|
|
5090
5257
|
type: 'object',
|
|
5091
5258
|
properties: {
|
|
5092
|
-
action: { type: 'string', enum: ['plan', 'apply', 'from_url'], description: 'plan (dry run), apply, or from_url (read a site into a proposed kit)' },
|
|
5259
|
+
action: { type: 'string', enum: ['plan', 'apply', 'from_url', 'set', 'get', 'clear'], description: 'plan (dry run), apply (once), set (apply + every new item inherits it), get, clear, or from_url (read a site into a proposed kit)' },
|
|
5093
5260
|
url: { type: 'string', description: "from_url: the product's site, http(s)." },
|
|
5094
5261
|
screenshots: { type: 'integer', minimum: 1, maximum: 6, description: 'from_url: screenshots down the page (default 3).' },
|
|
5095
5262
|
kit: {
|
|
@@ -5244,13 +5411,13 @@ ACTIONS:
|
|
|
5244
5411
|
- lineage: Walk back to the roots — the chain of imports/derivations that produced it. Params: itemId
|
|
5245
5412
|
- dependents: What breaks if you delete it. Params: itemId — check this BEFORE removing something
|
|
5246
5413
|
other items were built from.
|
|
5247
|
-
- record: Note
|
|
5414
|
+
- record: Note where this item came from. Params: itemId, kind (${LINEAGE_KINDS.join(' | ')}; default derivedFrom), sourceRef (an item id, or asset:… / component:…), meta`,
|
|
5248
5415
|
inputSchema: {
|
|
5249
5416
|
type: 'object',
|
|
5250
5417
|
properties: {
|
|
5251
5418
|
action: { type: 'string', enum: ['get', 'lineage', 'dependents', 'record'] },
|
|
5252
5419
|
itemId: { type: 'string' },
|
|
5253
|
-
kind: { type: 'string',
|
|
5420
|
+
kind: { type: 'string', enum: [...LINEAGE_KINDS], description: 'record: lineage kind (default derivedFrom)' },
|
|
5254
5421
|
sourceRef: { type: 'string', description: 'What it came from (item id, asset id, URL)' },
|
|
5255
5422
|
meta: { type: 'object' },
|
|
5256
5423
|
},
|
|
@@ -5386,7 +5553,7 @@ ACTIONS:
|
|
|
5386
5553
|
idempotentHint: false,
|
|
5387
5554
|
openWorldHint: false,
|
|
5388
5555
|
},
|
|
5389
|
-
description: `Apply
|
|
5556
|
+
description: `Apply image filters to raster items: a GPU set, plus painted watercolor / painterly looks for photos.
|
|
5390
5557
|
|
|
5391
5558
|
ACTIONS:
|
|
5392
5559
|
- apply: Apply a single filter. Params: itemId, filterName, params
|
|
@@ -5399,6 +5566,8 @@ Available filters (GPU raster set):
|
|
|
5399
5566
|
- Stylize: vignette (intensity, radius), edgeDetect, halftoneDots (size, angle), halftoneCMYK (size), dither (levels), grain (amount, seed), scanlines (intensity, period), chromaticAberration (amount), blur (radius)
|
|
5400
5567
|
- Light (additive): bloom (amount, threshold, radius), glow, halation (warm-fringed bloom; color), lightShafts (amount, threshold, decay, density, x, y, color — volumetric rays from a point)
|
|
5401
5568
|
- Keying: lumaKey (threshold, softness, invert)
|
|
5569
|
+
- Pixel art: pixelate (size or cells across, levels, dither, palette [colours] — e.g. a PICO-8 set), where the studio has it
|
|
5570
|
+
- Painted (CPU, STILL images only, where the studio has them): watercolor (strength, seed, radius, edges, bleed, granulation, paper, washes — layered washes, pigment rims, paper grain), painterly (strength, seed, radius, stroke, texture, edges, vivid — brush strokes along the form). About 1 s per 1080p image; the same seed paints the same picture; values are clamped and unknown params are warned about. A video is refused (they cannot run per frame): apply to a still or a captured frame. A studio without them refuses with its list of filters.
|
|
5402
5571
|
- SECOND-INPUT (params.map = another item's id, resolved to its pixels): displace (amount, dispersion, map, mapChannel — the map's brightness pushes pixels), refract (displace with per-channel dispersion — glass), trackMatte (channel, invert, strength — prefer pinepaper_media apply_track_matte for the live version), datamosh (amount, block, map? — self-moshes without a map)
|
|
5403
5572
|
|
|
5404
5573
|
RECIPE — grunge poster: chain [grain, scanlines, vignette]. Glass header: refract with map = a gradient item.
|
|
@@ -5636,6 +5805,8 @@ ACTIONS:
|
|
|
5636
5805
|
- choose: Mood- and context-weighted recommendation based on subject/prompt words and target feeling.
|
|
5637
5806
|
- coverage: Report total indexable and describable capabilities across categories.
|
|
5638
5807
|
- find: Direct lookup of a capability by key to inspect its description and how to apply it (applyWith).
|
|
5808
|
+
- catalogue: { catalogue: 'rig_presets' | 'shader_effects' | 'stroke_decorations' | 'precomps' | 'images' | 'segment_edit_kinds' | 'shatter_orders' | 'world_meshes' | 'relation_presets' } — the registries 'list' does not gather.
|
|
5809
|
+
- studio: {} — which engine methods THIS studio has, so you learn what will refuse before calling it.
|
|
5639
5810
|
|
|
5640
5811
|
MOOD VOCABULARY FOR CHOOSE:
|
|
5641
5812
|
- reveal: resolve, reveal, appear, emerge, decode, decrypt, unscramble, form, assemble
|
|
@@ -5833,7 +6004,8 @@ ACTIONS:
|
|
|
5833
6004
|
BEHAVIOR TYPES: repel, attract, follow, orbit, slingshot, physics_body, draggable_constrained
|
|
5834
6005
|
- repel params: strength, maxDistance, minDistance, returnSpeed (0–1, default 0.08 — how fast the item eases back to its home position once the target/cursor leaves; returnSpeed: 0 freezes it scattered instead of reforming).
|
|
5835
6006
|
|
|
5836
|
-
ACTION TYPES: navigate, show, hide, animate, stopAnimation, setState, incrementScore, playTimeline, pauseTimeline, seekTimeline, showFeedback, complete
|
|
6007
|
+
ACTION TYPES: navigate, show, hide, animate, stopAnimation, setState, incrementScore, playTimeline, pauseTimeline, seekTimeline, showFeedback, complete
|
|
6008
|
+
ACTION PARAMS: show / hide / stopAnimation: { items: [ids] } (itemId is accepted); animate: { items, animation?, speed? }; incrementScore: { points } (default 1; amount is accepted); setState: { key, value }.`,
|
|
5837
6009
|
inputSchema: {
|
|
5838
6010
|
type: 'object',
|
|
5839
6011
|
properties: {
|
|
@@ -5948,7 +6120,7 @@ TEMPLATE CATEGORIES (13):
|
|
|
5948
6120
|
EXAMPLES:
|
|
5949
6121
|
- List all templates: { "listOnly": true }
|
|
5950
6122
|
- List by category: { "category": "social-media", "listOnly": true }
|
|
5951
|
-
- Load template: { "templateId": "solar-system" }`,
|
|
6123
|
+
- Load template: { "templateId": "solar-system-education" }`,
|
|
5952
6124
|
inputSchema: {
|
|
5953
6125
|
type: 'object',
|
|
5954
6126
|
properties: {
|
|
@@ -6129,7 +6301,7 @@ RETURNS:
|
|
|
6129
6301
|
properties: {
|
|
6130
6302
|
id: {
|
|
6131
6303
|
type: 'string',
|
|
6132
|
-
description: '
|
|
6304
|
+
description: 'Requested connector id. The studio may not honour it (the result says idHonoured): always use the connectorId the result returns in pinepaper_update_connector / pinepaper_remove_connector.',
|
|
6133
6305
|
},
|
|
6134
6306
|
sourceItemId: {
|
|
6135
6307
|
type: 'string',
|
|
@@ -6226,7 +6398,7 @@ Same as pinepaper_connect: routing, lineColor, lineWidth, lineStyle, headStyle,
|
|
|
6226
6398
|
properties: {
|
|
6227
6399
|
id: {
|
|
6228
6400
|
type: 'string',
|
|
6229
|
-
description: '
|
|
6401
|
+
description: 'Requested connector id. The studio may not honour it (the result says idHonoured): always use the connectorId the result returns in pinepaper_update_connector / pinepaper_remove_connector.',
|
|
6230
6402
|
},
|
|
6231
6403
|
sourceItemId: {
|
|
6232
6404
|
type: 'string',
|
|
@@ -6392,7 +6564,7 @@ EXAMPLES:
|
|
|
6392
6564
|
- Grid of icons: {layoutType: "grid", options: {columns: 4, cellWidth: 120}}
|
|
6393
6565
|
|
|
6394
6566
|
PARTIAL LAYOUT:
|
|
6395
|
-
Pass itemIds array to only layout specific items, leaving others in place.`,
|
|
6567
|
+
Pass itemIds array to only layout specific items, leaving others in place. They are CANVAS ids (item_N) — after pinepaper_import_mermaid, use its nodeItemIds, not the mermaid ids (A, B…), which lay out nothing.`,
|
|
6396
6568
|
inputSchema: {
|
|
6397
6569
|
type: 'object',
|
|
6398
6570
|
properties: {
|
|
@@ -6404,7 +6576,7 @@ Pass itemIds array to only layout specific items, leaving others in place.`,
|
|
|
6404
6576
|
itemIds: {
|
|
6405
6577
|
type: 'array',
|
|
6406
6578
|
items: { type: 'string' },
|
|
6407
|
-
description: '
|
|
6579
|
+
description: 'Canvas item ids (item_N) to layout (default: all diagram items). After import_mermaid, its nodeItemIds.',
|
|
6408
6580
|
},
|
|
6409
6581
|
options: {
|
|
6410
6582
|
type: 'object',
|
|
@@ -6667,14 +6839,13 @@ EXAMPLES:
|
|
|
6667
6839
|
idempotentHint: false,
|
|
6668
6840
|
openWorldHint: false,
|
|
6669
6841
|
},
|
|
6670
|
-
description: `Load a map and
|
|
6842
|
+
description: `Load a map and import/export its config. Call action: "load" first.
|
|
6671
6843
|
|
|
6672
6844
|
ACTIONS:
|
|
6673
|
-
- load — { mapId: "usa"|"world"|"worldHighRes"|<custom>, projection?, options? }
|
|
6674
|
-
- pan
|
|
6675
|
-
- zoom — { level, duration? }
|
|
6845
|
+
- load — { mapId: "usa"|"world"|"worldHighRes"|<custom>, projection?, center?: [lon, lat], options? } — center is the projection's centre; options.scale is the d3 projection scale, UNIT-LESS (hundreds: about 600 fills India on a 1080-tall canvas). center alone does not zoom
|
|
6846
|
+
- pan, zoom — REFUSED: the map has no view transform, and a camera move would change only the studio view, which a canvas export ignores. For a moving frame, keyframe a camera animation and export with framing:"camera"
|
|
6676
6847
|
- export_config — {}
|
|
6677
|
-
- import_custom — { url? | geojson?,
|
|
6848
|
+
- import_custom — { url? | geoJson? (geojson accepted), options?: { projection?, fillColor? } }
|
|
6678
6849
|
|
|
6679
6850
|
For region styling/selection use pinepaper_map_regions; for animations use pinepaper_map_animation; for CSV/GeoJSON/source-info use pinepaper_map_data.`,
|
|
6680
6851
|
inputSchema: {
|
|
@@ -6683,6 +6854,7 @@ For region styling/selection use pinepaper_map_regions; for animations use pinep
|
|
|
6683
6854
|
action: { type: 'string', enum: ['load', 'pan', 'zoom', 'export_config', 'import_custom'], description: 'Map action' },
|
|
6684
6855
|
mapId: { type: 'string', description: 'Map identifier (load)' },
|
|
6685
6856
|
projection: { type: 'string', description: 'Projection (load / import_custom)' },
|
|
6857
|
+
center: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2, description: 'load: [lon, lat] the projection centres on' },
|
|
6686
6858
|
options: { type: 'object', description: 'Map options (load)', additionalProperties: true },
|
|
6687
6859
|
lat: { type: 'number', description: 'Latitude (pan)' },
|
|
6688
6860
|
lon: { type: 'number', description: 'Longitude (pan)' },
|
|
@@ -6706,15 +6878,17 @@ For region styling/selection use pinepaper_map_regions; for animations use pinep
|
|
|
6706
6878
|
description: `Highlight, color, label, and select map regions. Requires a map loaded via pinepaper_map { action: "load" } first.
|
|
6707
6879
|
|
|
6708
6880
|
ACTIONS:
|
|
6709
|
-
- highlight — { regionIds: string[], color?, opacity? }
|
|
6881
|
+
- highlight — { regionIds: string[], color?, opacity? } opacity 0..1 tints over the land: pre-blended over the region's land fill, drawn opaque
|
|
6710
6882
|
- unhighlight — { regionIds?: string[] } omit to clear all
|
|
6711
6883
|
- apply_colors — { data: Record<regionId, value>, options? } choropleth coloring
|
|
6712
6884
|
- add_marker — { lat, lon, label?, color?, size? }
|
|
6713
6885
|
- add_labels — { regions?: string[], style?, formatter? }
|
|
6714
|
-
- get_at_point — { x, y } region at canvas coords
|
|
6886
|
+
- get_at_point — { x, y } region id + name at canvas coords, and its (lon, lat)
|
|
6715
6887
|
- select — { regionIds: string[] }
|
|
6716
6888
|
- deselect — { regionIds?: string[] } omit to deselect all
|
|
6717
|
-
- get_highlighted — {}
|
|
6889
|
+
- get_highlighted — {} every region highlight OR apply_colors coloured (one colour channel)
|
|
6890
|
+
|
|
6891
|
+
highlight and apply_colors return the regions they coloured, by id, and name any id not on the map (notFound / unmatched).`,
|
|
6718
6892
|
inputSchema: {
|
|
6719
6893
|
type: 'object',
|
|
6720
6894
|
properties: {
|
|
@@ -6749,19 +6923,19 @@ ACTIONS:
|
|
|
6749
6923
|
description: `Animate map regions with keyframes or wave effects.
|
|
6750
6924
|
|
|
6751
6925
|
ACTIONS:
|
|
6752
|
-
- animate_regions — { regions:
|
|
6753
|
-
- animate_wave — {
|
|
6926
|
+
- animate_regions — { regions: { [regionId]: [{ time, fillColor (or color), opacity?, easing? }, …at least 2] }, duration?, loop? }
|
|
6927
|
+
- animate_wave — { colors: [at least 2], waveDirection? (horizontal | vertical | radial), waveSpeed?, duration?, loop? } — the wave runs across ALL regions
|
|
6754
6928
|
- stop — { regionIds?: string[] }
|
|
6755
6929
|
- get_animated — {}`,
|
|
6756
6930
|
inputSchema: {
|
|
6757
6931
|
type: 'object',
|
|
6758
6932
|
properties: {
|
|
6759
6933
|
action: { type: 'string', enum: ['animate_regions', 'animate_wave', 'stop', 'get_animated'], description: 'Animation action' },
|
|
6760
|
-
regions: {
|
|
6761
|
-
|
|
6934
|
+
regions: { description: 'animate_regions: an object of keyframes per region id. stop: an array of region ids to stop (omit for all).' },
|
|
6935
|
+
resetColors: { type: 'boolean', description: 'stop: reset regions to their default colours (default true).' },
|
|
6936
|
+
colors: { type: 'array', items: { type: 'string' }, description: 'animate_wave: colours the wave cycles through (at least 2).' },
|
|
6762
6937
|
waveDirection: { type: 'string', enum: ['horizontal', 'vertical', 'radial'] },
|
|
6763
|
-
|
|
6764
|
-
color: { type: 'string' },
|
|
6938
|
+
waveSpeed: { type: 'number' },
|
|
6765
6939
|
duration: { type: 'number' },
|
|
6766
6940
|
loop: { type: 'boolean' },
|
|
6767
6941
|
},
|
|
@@ -6780,8 +6954,8 @@ ACTIONS:
|
|
|
6780
6954
|
description: `CSV / GeoJSON import-export and source-info introspection.
|
|
6781
6955
|
|
|
6782
6956
|
ACTIONS:
|
|
6783
|
-
- export_csv — {
|
|
6784
|
-
- import_csv — { csv: string,
|
|
6957
|
+
- export_csv — { includeColors?, includeHighlighted?, includeSelected?, download?, filename? } — one row per region of the loaded map
|
|
6958
|
+
- import_csv — { csv (or csvText): string, applyColors?, applyHighlight?, applySelection? } — the CSV export_csv writes (one row per region id)
|
|
6785
6959
|
- export_geojson — { includeStyles?, includeMetadata?, selectedOnly?, download?, filename? }
|
|
6786
6960
|
- export_original_geojson — { download?, filename? } source GeoJSON before styling
|
|
6787
6961
|
- source_info — {} { source, projection, quality, regionCount, hasOriginalGeoJSON, isCustomImport }`,
|
|
@@ -6789,10 +6963,14 @@ ACTIONS:
|
|
|
6789
6963
|
type: 'object',
|
|
6790
6964
|
properties: {
|
|
6791
6965
|
action: { type: 'string', enum: ['export_csv', 'import_csv', 'export_geojson', 'export_original_geojson', 'source_info'], description: 'Data action' },
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6966
|
+
csv: { type: 'string', description: 'import_csv: the CSV text (csvText is accepted too).' },
|
|
6967
|
+
csvText: { type: 'string' },
|
|
6968
|
+
applyColors: { type: 'boolean' },
|
|
6969
|
+
applyHighlight: { type: 'boolean' },
|
|
6970
|
+
applySelection: { type: 'boolean' },
|
|
6971
|
+
includeColors: { type: 'boolean' },
|
|
6972
|
+
includeHighlighted: { type: 'boolean' },
|
|
6973
|
+
includeSelected: { type: 'boolean' },
|
|
6796
6974
|
includeStyles: { type: 'boolean' },
|
|
6797
6975
|
includeMetadata: { type: 'boolean' },
|
|
6798
6976
|
selectedOnly: { type: 'boolean' },
|
|
@@ -7148,7 +7326,7 @@ USE WHEN:
|
|
|
7148
7326
|
- "grid background", "geometric abstract", "noise texture"
|
|
7149
7327
|
- Creating dynamic procedural backgrounds
|
|
7150
7328
|
|
|
7151
|
-
GENERATORS (
|
|
7329
|
+
GENERATORS (highlights; all ${GeneratorNameSchema.options.length} are in the generatorName enum):
|
|
7152
7330
|
|
|
7153
7331
|
Classic:
|
|
7154
7332
|
- drawSunburst: Radial rays from center (rayCount, colors, bgColor, opacity, rayGap, gradientRays)
|
|
@@ -7276,6 +7454,8 @@ USE WHEN:
|
|
|
7276
7454
|
},
|
|
7277
7455
|
description: `Apply a visual effect to an item.
|
|
7278
7456
|
|
|
7457
|
+
EFFECT OR EMITTER: apply_effect is a live look on an item. For particles a saved scene, a template, the cloud render and every export must reproduce frame for frame — confetti at 2 s, a trail behind a moving item — use pinepaper_emitter: its particles are a pure function of its spec and the scene time.
|
|
7458
|
+
|
|
7279
7459
|
USE WHEN:
|
|
7280
7460
|
- Adding sparkle/glitter effects
|
|
7281
7461
|
- Creating burst/explosion, fire, smoke effects
|
|
@@ -7292,7 +7472,7 @@ glow's own default is 1.5. Pass a pixel count here and you will be wrong by
|
|
|
7292
7472
|
about two orders of magnitude. A recent studio warns about it
|
|
7293
7473
|
(kind: 'glow-size-unit'); an older one does not.
|
|
7294
7474
|
|
|
7295
|
-
EFFECTS (
|
|
7475
|
+
EFFECTS (${EffectTypeSchema.options.length}: particles, then the shader auras from heatmap on):
|
|
7296
7476
|
- sparkle: Glitter/sparkle particles (color, speed, size)
|
|
7297
7477
|
- blast: Explosion burst effect (color, radius, count)
|
|
7298
7478
|
- smoke: Rising smoke plumes (color, speed, size, drift, height, growthRate)
|
|
@@ -7362,27 +7542,17 @@ USE WHEN:
|
|
|
7362
7542
|
- Post-processing the entire scene
|
|
7363
7543
|
- User says "make it look vintage", "add blur", "black and white"
|
|
7364
7544
|
|
|
7365
|
-
AVAILABLE FILTERS:
|
|
7366
|
-
|
|
7367
|
-
|
|
7368
|
-
- blur: Gaussian blur effect (params: radius 0-20)
|
|
7369
|
-
- brightness: Adjust brightness (params: value -100 to 100)
|
|
7370
|
-
- contrast: Adjust contrast (params: value -100 to 100)
|
|
7371
|
-
- saturation: Adjust color saturation (params: value -100 to 100). 'saturate' is accepted as the same filter.
|
|
7372
|
-
- invert: Invert colors (params: intensity 0-1)
|
|
7373
|
-
- noise: Add film grain (params: intensity 0-100, monochrome: true/false)
|
|
7374
|
-
- vignette: Darken edges (params: intensity 0-1, radius 0-1)
|
|
7375
|
-
- vintage: Preset combining sepia, vignette, noise
|
|
7376
|
-
- colorOverlay: Add color tint (params: color, intensity, blendMode)
|
|
7377
|
-
- sharpen: Increase sharpness (params: intensity 0-100)
|
|
7378
|
-
- emboss: 3D emboss (params: intensity 0-1)
|
|
7379
|
-
- posterize: Reduce color levels (params: levels 2-32)
|
|
7545
|
+
AVAILABLE FILTERS (the engine's own list, with each one's parameters):
|
|
7546
|
+
${FILTER_TYPES.map((f) => `- ${f}: ${FILTER_DOCS[f]}`).join('\n')}
|
|
7547
|
+
'saturate' is accepted as saturation.
|
|
7380
7548
|
|
|
7381
7549
|
EXAMPLES:
|
|
7382
7550
|
- "Make it black and white" → filterType: "grayscale"
|
|
7383
7551
|
- "Add vintage look" → filterType: "vintage", params: {intensity: 0.8}
|
|
7384
7552
|
- "Blur the background" → filterType: "blur", params: {radius: 5}
|
|
7385
7553
|
- "Increase contrast" → filterType: "contrast", params: {value: 30}
|
|
7554
|
+
- "Print it like a comic" → filterType: "halftoneCMYK", params: {size: 6}
|
|
7555
|
+
- "8-bit / retro screen" → filterType: "dither", params: {levels: 4}
|
|
7386
7556
|
|
|
7387
7557
|
Filters can be stacked - call multiple times to combine effects.`,
|
|
7388
7558
|
inputSchema: {
|
|
@@ -7390,7 +7560,7 @@ Filters can be stacked - call multiple times to combine effects.`,
|
|
|
7390
7560
|
properties: {
|
|
7391
7561
|
filterType: {
|
|
7392
7562
|
type: 'string',
|
|
7393
|
-
enum: [
|
|
7563
|
+
enum: [...FILTER_TYPES],
|
|
7394
7564
|
description: 'Type of filter to apply',
|
|
7395
7565
|
},
|
|
7396
7566
|
params: {
|
|
@@ -7543,28 +7713,403 @@ OUTPUT FORMAT:
|
|
|
7543
7713
|
idempotentHint: true,
|
|
7544
7714
|
openWorldHint: false,
|
|
7545
7715
|
},
|
|
7546
|
-
description: `Export the
|
|
7716
|
+
description: `Export the scene: a SUMMARY for inspection by default, or with full: true a RESTORABLE project document saved to a file.
|
|
7717
|
+
|
|
7718
|
+
SUMMARY (default) — for reading the scene, not for saving it:
|
|
7719
|
+
- items (id, type, position, bounds, active window), relations, decorative items, backgroundColor, canvasSize
|
|
7720
|
+
- It does NOT carry keyframe tracks, rigging or scene chains, so a scene rebuilt from it has no motion. The result says so (note).
|
|
7721
|
+
|
|
7722
|
+
FULL (full: true) — to save work and restore it later:
|
|
7723
|
+
- The engine's own project document (relations, keyframes, rigging, connectors, scene chains, animation config), written to a file because it can be megabytes.
|
|
7724
|
+
- Returns filePath, bytes and counts (items, animated items, keyframes, relations). Restore with pinepaper_import_scene { path } and compare the counts.
|
|
7547
7725
|
|
|
7548
7726
|
USE WHEN:
|
|
7549
|
-
-
|
|
7550
|
-
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7727
|
+
- full: true — saving a scene to come back to, a backup, a hand-off to another session
|
|
7728
|
+
- summary — debugging composition, checking what is on the canvas`,
|
|
7729
|
+
inputSchema: {
|
|
7730
|
+
type: 'object',
|
|
7731
|
+
properties: {
|
|
7732
|
+
full: { type: 'boolean', description: 'Save a restorable project document to a file instead of returning the summary.' },
|
|
7733
|
+
name: { type: 'string', description: 'full: a name recorded in the document.' },
|
|
7734
|
+
},
|
|
7735
|
+
},
|
|
7736
|
+
},
|
|
7737
|
+
{
|
|
7738
|
+
name: 'pinepaper_styled_scene',
|
|
7739
|
+
annotations: {
|
|
7740
|
+
title: 'Styled Scene',
|
|
7741
|
+
readOnlyHint: false,
|
|
7742
|
+
destructiveHint: false,
|
|
7743
|
+
idempotentHint: true,
|
|
7744
|
+
openWorldHint: false,
|
|
7745
|
+
},
|
|
7746
|
+
description: `One composition, drawn in a built-in STYLE, as one canvas item that redraws itself every exported frame. No drawing code: pick a style, optionally shape the composition, then export.
|
|
7553
7747
|
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7748
|
+
STYLES: ${STYLED_SCENE_STYLES.map((k) => `${k} (${STYLED_SCENE_STYLE_INFO[k]})`).join('; ')}. Default watercolor; the studio also takes its aliases (watercolour, papercut, 1bit…), and an unknown style is refused with the list.
|
|
7749
|
+
|
|
7750
|
+
THE COMPOSITION is data (spec). Every key is optional; omitted keys take the 'valley' preset, a valley going from dream to cost over 8 s: size [W, H], duration, transition {name, start, length}, camera {zoom: [from, to], pan}, palette, sun, clouds, ridges, foreground, river, settlements, structure, pylons, vegetation, effects {steam, ripples, scars}, hud {label}.
|
|
7751
|
+
|
|
7752
|
+
THEN EXPORT FOR ITS DURATION: the result carries duration, and the scene runs that long. Pass it as agent_export's duration; a different length cuts the scene short or holds its last frame.
|
|
7753
|
+
|
|
7754
|
+
KNOW:
|
|
7755
|
+
- Calling again with the same id REPLACES the scene, which is how to change style.
|
|
7756
|
+
- dither and ascii need a Chrome renderer (WebKit ignores the canvas filter they use).
|
|
7757
|
+
- The scene redraws every frame through a render hook. After a page reload it is a still image until this is called again.
|
|
7758
|
+
- It sits below every other item, sized to cover the canvas, so titles and overlays go on top as usual.
|
|
7759
|
+
|
|
7760
|
+
EXAMPLE: { style: 'ink', duration: 6 }`,
|
|
7560
7761
|
inputSchema: {
|
|
7561
7762
|
type: 'object',
|
|
7562
|
-
properties: {
|
|
7763
|
+
properties: {
|
|
7764
|
+
style: { type: 'string', description: `${STYLED_SCENE_STYLES.join(' | ')} (default watercolor)` },
|
|
7765
|
+
duration: { type: 'number', description: 'Seconds the scene runs (default 8). Export for this long.' },
|
|
7766
|
+
id: { type: 'string', description: 'Scene id (default "styled-scene"); the same id replaces the scene.' },
|
|
7767
|
+
spec: { type: 'object', description: "The composition; omitted keys take the 'valley' preset.", additionalProperties: true },
|
|
7768
|
+
},
|
|
7769
|
+
},
|
|
7770
|
+
},
|
|
7771
|
+
{
|
|
7772
|
+
name: 'pinepaper_look',
|
|
7773
|
+
annotations: { title: 'Look', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
7774
|
+
description: `Restyle what is already drawn — one item, a group and everything in it, or the WHOLE SCENE — as a named look: palette remapped in place (colour keyframes too), strokes, type, image treatment, texture, background and a framing device. Ids, keyframes and relations are kept; it survives a project round trip; remove restores it pixel for pixel. This is "the same subject, in style X".
|
|
7775
|
+
|
|
7776
|
+
ACTIONS:
|
|
7777
|
+
- list: every look with its FIDELITY — the engine's own grade: native (the medium itself), faithful (close, its approximations named), impression (an evocation, not the real style).
|
|
7778
|
+
- describe: { look } — the recipe, its fidelity, what it approximates, and the engine call per part. Read it before promising a look.
|
|
7779
|
+
- apply: { look, target? ('scene' default | an itemId), scope? ('item' | 'subtree' | 'scene') } → what it restyled, and the fidelity. An impression comes with a warning — present it as an approximation, not the finished style; a faithful look lists what it approximates.
|
|
7780
|
+
- remove: { target } — restore what was there. get: { target } — the look on it now.
|
|
7781
|
+
- grid: { looks?, cols?, cellWidth?, t? } — a contact sheet of the scene in several looks, to choose from: the PNG is the reply's \`data\` (a data URL, as agent_export returns it), with cells [{ id, name, fidelity }].
|
|
7782
|
+
|
|
7783
|
+
look is an id from list, or a look spec. An unknown look is refused with the known ones.
|
|
7784
|
+
Looks act on what is THERE: they restyle a drawing, they do not redraw a subject from scratch in another medium.`,
|
|
7785
|
+
inputSchema: {
|
|
7786
|
+
type: 'object',
|
|
7787
|
+
properties: {
|
|
7788
|
+
action: { type: 'string', enum: ['list', 'describe', 'apply', 'remove', 'get', 'grid'] },
|
|
7789
|
+
look: { description: 'a look id, or a look spec', oneOf: [{ type: 'string' }, { type: 'object', additionalProperties: true }] },
|
|
7790
|
+
target: { type: 'string', description: "an itemId, or 'scene' (default)" },
|
|
7791
|
+
scope: { type: 'string', enum: ['item', 'subtree', 'scene'] },
|
|
7792
|
+
looks: { type: 'array', items: { type: 'string' } },
|
|
7793
|
+
cols: { type: 'integer' }, cellWidth: { type: 'number' }, t: { type: 'number' },
|
|
7794
|
+
},
|
|
7795
|
+
required: ['action'],
|
|
7796
|
+
},
|
|
7797
|
+
},
|
|
7798
|
+
{
|
|
7799
|
+
name: 'pinepaper_original_character',
|
|
7800
|
+
annotations: { title: 'Original Character', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
7801
|
+
description: `Make an ORIGINAL character from a base rig and a declarative variant — no drawing code, and nothing anyone else owns. Every variant keeps its base's parts and rig, so expressions, walks, poses and pinepaper_choreograph (which drives its root) all work on it.
|
|
7802
|
+
|
|
7803
|
+
ACTIONS:
|
|
7804
|
+
- bases: the bases (person, robot, cat, bird, blob) with their roles.
|
|
7805
|
+
- describe: { base? } — every legal palette key, feature value, accessory (and its slot), proportion range and look. READ IT FIRST: a value it does not list is refused by name and nothing is drawn.
|
|
7806
|
+
- random: { base, seed, fixed? } — a distinct, valid variant; the same seed gives the same one. fixed is shared by every member (a look, proportions) while each draws its own signature — a cast of 12 is twelve seeds with one fixed.
|
|
7807
|
+
- create: { base, variant?: { palette, features, look, seed, name }, at: { x, y }, height? (320) | scale?, rig? (true) } → itemId (the root), rigId, parts { role: id }. y is the FEET.
|
|
7808
|
+
|
|
7809
|
+
FEATURES: hair, eyes, eyeColor, nose, mouth, ears, brows, accessories (12 slots, one per slot), proportions { headScale, bodyScale, limbLength }.
|
|
7810
|
+
LOOKS on a rig: flat, ink, charcoal, paper-cutout, pixel. watercolor / oil / hatch are refused with the reason.
|
|
7811
|
+
|
|
7812
|
+
INTERACTION (pinepaper_add_relation, source = the character's itemId):
|
|
7813
|
+
- looks_at → an item id, or target 'cursor': the face follows it.
|
|
7814
|
+
- reacts_to → an event or another character; params reaction (hop | jolt | shake | nod | an expression), after, duration, amount.
|
|
7815
|
+
- drag_to_turn (no target): dragging turns the head; params max, sensitivity, turn (keyframeable), spring.
|
|
7816
|
+
- a sound on the reaction: on_event_play_sound on the same event.
|
|
7817
|
+
|
|
7818
|
+
EXAMPLE: { action: 'create', base: 'person', at: { x: 480, y: 900 }, variant: { palette: { skin: '#e8b48a', hair: '#2b1d16', outfit: '#3a7bd5', accent: '#f2c14e' }, features: { hair: 'bun', eyes: 'almond', mouth: 'smile', accessories: ['cape'] }, look: 'ink', name: 'Mira' } }`,
|
|
7819
|
+
inputSchema: {
|
|
7820
|
+
type: 'object',
|
|
7821
|
+
properties: {
|
|
7822
|
+
action: { type: 'string', enum: ['bases', 'describe', 'random', 'create'] },
|
|
7823
|
+
base: { type: 'string' }, variant: { type: 'object', additionalProperties: true },
|
|
7824
|
+
seed: { type: 'integer' }, fixed: { type: 'object', additionalProperties: true },
|
|
7825
|
+
at: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } } },
|
|
7826
|
+
height: { type: 'number' }, scale: { type: 'number' }, rig: { type: 'boolean' },
|
|
7827
|
+
},
|
|
7828
|
+
required: ['action'],
|
|
7829
|
+
},
|
|
7830
|
+
},
|
|
7831
|
+
{
|
|
7832
|
+
name: 'pinepaper_morph_sequence',
|
|
7833
|
+
annotations: { title: 'Morph Sequence', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
7834
|
+
description: `ONE element through many states, with no cuts: each state grows out of the last (a pill becomes an input field becomes a dot becomes a card). The one-take UI-launch idiom, from one call.
|
|
7835
|
+
|
|
7836
|
+
{ itemId, states: [{ at, duration? (0.8), to: itemId | shape: { itemType, position, properties? } }], easing?, camera?, sound? }
|
|
7837
|
+
- Each state is the shape the element morphs INTO, starting at \`at\` seconds. Name an existing item (to) or describe one (shape); either way the reference is hidden and the element keeps its own id throughout. States run one after another; an overlap is refused.
|
|
7838
|
+
- easing: linear | easeIn | easeOut | easeInOut (default easeInOut) — the four the morph supports.
|
|
7839
|
+
- camera: true (or { fill, easing }) keys the camera on the SAME timing: it holds while a state rests and moves with each morph, framing the new state to \`fill\` of the frame (default 0.45).
|
|
7840
|
+
- sound: true puts a whoosh at each morph start (or name any catalogue sound).
|
|
7841
|
+
|
|
7842
|
+
Shapes morph by kind: path → path point by point, circle → circle by radius, text → text by font, across kinds by a particle transition. Colours and size morph too.
|
|
7843
|
+
|
|
7844
|
+
RETURNS: states [{ at, duration, targetId }], end (seconds), camera { keyframes }, sounds.
|
|
7845
|
+
|
|
7846
|
+
EXAMPLE — pill → field → dot:
|
|
7847
|
+
{ itemId: "item_1", camera: true, sound: true, states: [
|
|
7848
|
+
{ at: 1, shape: { itemType: "rectangle", position: { x: 960, y: 540 }, properties: { width: 640, height: 88, cornerRadius: 44, color: "#ffffff" } } },
|
|
7849
|
+
{ at: 3, shape: { itemType: "circle", position: { x: 960, y: 540 }, properties: { radius: 18, color: "#111111" } } } ] }`,
|
|
7850
|
+
inputSchema: {
|
|
7851
|
+
type: 'object',
|
|
7852
|
+
properties: {
|
|
7853
|
+
itemId: { type: 'string' },
|
|
7854
|
+
states: { type: 'array', items: { type: 'object', properties: { at: { type: 'number' }, duration: { type: 'number' }, to: { type: 'string' }, shape: { type: 'object', additionalProperties: true } }, required: ['at'] } },
|
|
7855
|
+
easing: { type: 'string', enum: ['linear', 'easeIn', 'easeOut', 'easeInOut'] },
|
|
7856
|
+
camera: { description: 'true, or {fill, easing}', oneOf: [{ type: 'boolean' }, { type: 'object', additionalProperties: true }] },
|
|
7857
|
+
sound: { description: 'true, or a catalogue sound name', oneOf: [{ type: 'boolean' }, { type: 'string' }] },
|
|
7858
|
+
},
|
|
7859
|
+
required: ['itemId', 'states'],
|
|
7860
|
+
},
|
|
7861
|
+
},
|
|
7862
|
+
{
|
|
7863
|
+
name: 'pinepaper_emitter',
|
|
7864
|
+
annotations: { title: 'Particle Emitter', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
7865
|
+
description: `Particles as an ITEM whose every frame is a pure function of its spec and the scene time — so the preview, capture_frames, every export, a saved project, a template and the cloud render all draw the same particles, scrubbing backwards included. The serialisable, cloud-safe choice; pinepaper_apply_effect is a live look on an item.
|
|
7866
|
+
|
|
7867
|
+
ACTIONS:
|
|
7868
|
+
- create: { spec } → itemId
|
|
7869
|
+
- set: { itemId, spec } — merge a partial spec onto the emitter's.
|
|
7870
|
+
|
|
7871
|
+
SPEC:
|
|
7872
|
+
- mode: 'burst' (default) — bursts: [{ t, x, y, count, …per-burst overrides }]: a pop of particles at scene time t.
|
|
7873
|
+
- mode: 'trail' (or give target) — target (an item id), rate (per second), emitWindows [[t0, t1], …], window, offset: particles shed from a moving item.
|
|
7874
|
+
- look and physics (per emitter or per burst): shape ('circle' | 'rect' | 'dash' | 'star' | 'confetti', or an array), palette [colours], size / sizes / sizeEnd, speed + angle ranges or velocities, gravity (px/s², +down), drag, friction, lifetime, fadeIn / fadeOut, spin, opacity, jitter, seed.
|
|
7875
|
+
An unknown shape, mode or bad range is refused with the engine's reason — never defaulted.
|
|
7876
|
+
|
|
7877
|
+
EXAMPLE — confetti at 2 s:
|
|
7878
|
+
{ action: 'create', spec: { bursts: [{ t: 2, x: 960, y: 400, count: 80 }], shape: 'confetti', palette: ['#f43f5e', '#facc15', '#22d3ee'], speed: [300, 700], angle: [-150, -30], gravity: 900, lifetime: 2.5, seed: 7 } }`,
|
|
7879
|
+
inputSchema: {
|
|
7880
|
+
type: 'object',
|
|
7881
|
+
properties: {
|
|
7882
|
+
action: { type: 'string', enum: ['create', 'set'] },
|
|
7883
|
+
itemId: { type: 'string', description: 'set: the emitter' },
|
|
7884
|
+
spec: { type: 'object', additionalProperties: true, description: 'create: the whole spec; set: the keys to change' },
|
|
7885
|
+
},
|
|
7886
|
+
required: ['action', 'spec'],
|
|
7887
|
+
},
|
|
7888
|
+
},
|
|
7889
|
+
{
|
|
7890
|
+
name: 'pinepaper_render_hook',
|
|
7891
|
+
annotations: { title: 'Render Hook', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
7892
|
+
description: `Custom per-frame drawing that a saved scene CARRIES: SOURCE TEXT, compiled and run under the governor's loop guards with seeded Math, saved in the project document, the template and undo, and re-installed on load. (A raw function added with code is not saved — pinepaper_export_scene { full: true } names any such as unserialized.)
|
|
7893
|
+
|
|
7894
|
+
ACTIONS:
|
|
7895
|
+
- register: { id, source, layer? ('above' | 'below'), deterministic? (true), seed? } — source evaluates to (ctx, t, info) => {}: ctx in canvas coordinates, t the scene time in seconds, info { width, height, frame, seed }. 'above' draws over every item, 'below' under them. deterministic reseeds Math.random from (seed, frame) each call, so a frame always draws the same.
|
|
7896
|
+
- unregister: { id } list: the saved hooks.
|
|
7897
|
+
|
|
7898
|
+
Reach for it when no tool draws the look (a scanline overlay, a custom texture pass). For particles, pinepaper_emitter is data rather than code.
|
|
7899
|
+
|
|
7900
|
+
EXAMPLE: { action: 'register', id: 'scanlines', layer: 'above', source: "(ctx, t, info) => { ctx.globalAlpha = 0.08; for (let y = (t * 40) % 4; y < info.height; y += 4) ctx.fillRect(0, y, info.width, 1); }" }`,
|
|
7901
|
+
inputSchema: {
|
|
7902
|
+
type: 'object',
|
|
7903
|
+
properties: {
|
|
7904
|
+
action: { type: 'string', enum: ['register', 'unregister', 'list'] },
|
|
7905
|
+
id: { type: 'string' }, source: { type: 'string' },
|
|
7906
|
+
layer: { type: 'string', enum: ['above', 'below'] },
|
|
7907
|
+
deterministic: { type: 'boolean' }, seed: { type: 'integer' },
|
|
7908
|
+
},
|
|
7909
|
+
required: ['action'],
|
|
7910
|
+
},
|
|
7911
|
+
},
|
|
7912
|
+
{
|
|
7913
|
+
name: 'pinepaper_choreograph',
|
|
7914
|
+
annotations: {
|
|
7915
|
+
title: 'Choreograph an Actor',
|
|
7916
|
+
readOnlyHint: false,
|
|
7917
|
+
destructiveHint: false,
|
|
7918
|
+
idempotentHint: false,
|
|
7919
|
+
openWorldHint: false,
|
|
7920
|
+
},
|
|
7921
|
+
description: `Make any item ACT: a list of beats compiled to one keyframe track with anticipation, squash and stretch, contact and settle — the timing a hand animator would key, from one call.
|
|
7922
|
+
|
|
7923
|
+
BEATS run one after another: [{ at, verb, ...params }]. Positions are the actor's CENTRE in canvas px; a squash keeps its BOTTOM on the ground.
|
|
7924
|
+
- pop: scales up from nothing with an overshoot (an entrance in place).
|
|
7925
|
+
- drop { to? }: falls and lands with a squash. Already in the air (after a fly), it falls from where it is; on the ground or below the landing point, it comes in from above the frame.
|
|
7926
|
+
- hop { to?, height? }: crouch, launch stretched, arc, land squashed, settle. Without to it hops in place.
|
|
7927
|
+
- bounce { times? (3), height?, decay? (0.6), to? }: bounces that lose height; with to it travels while bouncing.
|
|
7928
|
+
- roll { to }: rolls without slipping — it turns by the distance over its radius (right = clockwise).
|
|
7929
|
+
- fly { to, arc? }: an arc through the air, nose up on the rise and down on the fall.
|
|
7930
|
+
- peek { edge (left|right|top|bottom), amount? (0.5), hold? (1) }: slides partly in from a frame edge, waits, ducks back out.
|
|
7931
|
+
- shake { intensity? }: a startled shiver. squash { amount? }: an in-place squash and recover (a reaction, a landing hit).
|
|
7932
|
+
- enter { from (edge), to? }: slides in from an edge with an overshoot. exit { edge }: a small anticipation, then away off that edge.
|
|
7933
|
+
Each verb has a default duration; pass duration to change it.
|
|
7934
|
+
|
|
7935
|
+
WHAT IT RETURNS: keyframes written, the duration, and where the actor ends (and whether that is on screen). Beats that overlap, an unknown verb, or roll / fly without a destination are refused by name.
|
|
7936
|
+
|
|
7937
|
+
ACTORS: a shape, a group, an imported SVG character, a photo cut-out — anything with one transform. Its own scale and rotation are kept (a beat squashes relative to them). A figure placed by pinepaper_character is several parts with their own tracks and no single transform, so choreograph its parts' group only if you grouped a STATIC figure; otherwise direct it with character beats.
|
|
7938
|
+
|
|
7939
|
+
SOUND: sound: true puts a sound on every moment the motion makes — the pop, each launch, every landing (a bounce's landings get quieter as the bounces shrink), a shake — placed on the timeline and baked into the video. sound: { contact: 'kick', whoosh: false, gain: 0.6 } picks or silences them by catalogue name (SFX beep, pop, wind, whoosh, zap; drums kick, snare, tom, hat, clap; or your own from define_sfx). The reply lists the sounds placed. A sound due before 0.06 s is placed at 0.06 s (movedFrom says so): an MP4 encoder swallows the first ~50 ms of audio.
|
|
7940
|
+
|
|
7941
|
+
MIX WITH: pinepaper_character / text expressions on top; pinepaper_apply_effect (confetti, shockwave) at a landing; pinepaper_sound at the same times. Call choreograph BEFORE keyframing the actor's other properties (colour, fill): the beat keys carry position, scale, rotation and opacity only, and the result warns if the actor already animates something else.
|
|
7942
|
+
|
|
7943
|
+
EXAMPLE — a ball comes to life and crosses the frame:
|
|
7944
|
+
{ itemId: "item_1", beats: [ { at: 0, verb: "pop" }, { at: 0.6, verb: "bounce", times: 3, to: [700, 820] }, { at: 2.4, verb: "roll", to: [1200, 820] }, { at: 3.5, verb: "fly", to: [1600, 300] } ] }`,
|
|
7945
|
+
inputSchema: {
|
|
7946
|
+
type: 'object',
|
|
7947
|
+
properties: {
|
|
7948
|
+
itemId: { type: 'string', description: 'The actor (any item with one transform).' },
|
|
7949
|
+
sound: { description: "true, or {pop?, whoosh?, contact?, shake? (catalogue name or false), gain? 0..1}: a sound on every pop, launch, landing and shake.", oneOf: [{ type: 'boolean' }, { type: 'object', additionalProperties: true }] },
|
|
7950
|
+
beats: {
|
|
7951
|
+
type: 'array',
|
|
7952
|
+
description: 'Beats in time order: [{at, verb, to?, duration?, height?, times?, decay?, arc?, edge?, from?, amount?, hold?, intensity?}].',
|
|
7953
|
+
items: {
|
|
7954
|
+
type: 'object',
|
|
7955
|
+
properties: {
|
|
7956
|
+
at: { type: 'number' },
|
|
7957
|
+
verb: { type: 'string', enum: ['pop', 'drop', 'hop', 'bounce', 'roll', 'fly', 'peek', 'shake', 'squash', 'enter', 'exit'] },
|
|
7958
|
+
to: { description: '[x, y] or {x, y}: the actor centre at the end of the beat' },
|
|
7959
|
+
duration: { type: 'number' }, height: { type: 'number' }, times: { type: 'integer' }, decay: { type: 'number' },
|
|
7960
|
+
arc: { type: 'number' }, edge: { type: 'string', enum: ['left', 'right', 'top', 'bottom'] }, from: { type: 'string', enum: ['left', 'right', 'top', 'bottom'] },
|
|
7961
|
+
amount: { type: 'number' }, hold: { type: 'number' }, intensity: { type: 'number' },
|
|
7962
|
+
},
|
|
7963
|
+
required: ['at', 'verb'],
|
|
7964
|
+
},
|
|
7965
|
+
},
|
|
7966
|
+
},
|
|
7967
|
+
required: ['itemId', 'beats'],
|
|
7968
|
+
},
|
|
7969
|
+
},
|
|
7970
|
+
{
|
|
7971
|
+
name: 'pinepaper_relight',
|
|
7972
|
+
annotations: {
|
|
7973
|
+
title: 'Relight the 3D World',
|
|
7974
|
+
readOnlyHint: false,
|
|
7975
|
+
destructiveHint: false,
|
|
7976
|
+
idempotentHint: true,
|
|
7977
|
+
openWorldHint: false,
|
|
7978
|
+
},
|
|
7979
|
+
description: `Light the 3D world layer (pinepaper_world3d) with point and directional lights — a post pass, so the lit side of every surface reads brighter in the preview and the export.
|
|
7980
|
+
|
|
7981
|
+
ACTIONS:
|
|
7982
|
+
- set: { lights, ambient?, strength?, enabled?, preview? } — install or replace the rig. Up to 8 lights:
|
|
7983
|
+
point: { type: 'point', x, y, z? (120), radius? (400), color?, intensity? (1), specular? (32) }
|
|
7984
|
+
directional: { type: 'directional', dir: [dx, dy, dz], color?, intensity? } [0, 0, -1] faces the viewer
|
|
7985
|
+
Coordinates are export-frame px; colours are '#rrggbb' or [r, g, b]. ambient default 0.2, strength (normal-map) default 2.
|
|
7986
|
+
The result reports cost: the pass runs on the CPU, about 0.42 s per 1080p frame, so a 10 s MP4 at 30 fps takes about 2 minutes longer.
|
|
7987
|
+
- get: the current rig.
|
|
7988
|
+
- clear: remove it.
|
|
7989
|
+
- list_lights: the light types.
|
|
7990
|
+
|
|
7991
|
+
LIMITS — say them before you promise a look:
|
|
7992
|
+
- Applied in the preview and in PNG, PDF, MP4 and WebM exports. GIF and APNG are NOT relit (agent_export says so when it happens).
|
|
7993
|
+
- Normals come from the world's brightness, not real depth, so it reads as raking light over the picture rather than true 3D shading.
|
|
7994
|
+
- It lights the 3D world layer only, not 2D items drawn over it.
|
|
7995
|
+
- The rig is NOT saved with the project: set it again after reloading.
|
|
7996
|
+
- A studio without the relight pass refuses with the reason.
|
|
7997
|
+
|
|
7998
|
+
EXAMPLE: { action: 'set', lights: [{ type: 'point', x: 1500, y: 200, z: 300, radius: 1200, color: '#ffd9a0', intensity: 1.4 }], ambient: 0.15 }`,
|
|
7999
|
+
inputSchema: {
|
|
8000
|
+
type: 'object',
|
|
8001
|
+
properties: {
|
|
8002
|
+
action: { type: 'string', enum: ['set', 'get', 'clear', 'list_lights'], description: 'Relight operation' },
|
|
8003
|
+
lights: { type: 'array', items: { type: 'object', additionalProperties: true }, description: "set: up to 8 lights, {type:'point', x, y, z?, radius?, color?, intensity?, specular?} or {type:'directional', dir:[dx,dy,dz], color?, intensity?}" },
|
|
8004
|
+
ambient: { type: 'number', description: 'set: ambient light (default 0.2)' },
|
|
8005
|
+
strength: { type: 'number', description: 'set: normal-map strength (default 2)' },
|
|
8006
|
+
enabled: { type: 'boolean', description: 'set: false keeps the rig but turns it off' },
|
|
8007
|
+
preview: { description: 'set: true / false, or {maxSide} for the live preview overlay', oneOf: [{ type: 'boolean' }, { type: 'object', properties: { maxSide: { type: 'number' } } }] },
|
|
8008
|
+
},
|
|
8009
|
+
required: ['action'],
|
|
8010
|
+
},
|
|
8011
|
+
},
|
|
8012
|
+
{
|
|
8013
|
+
name: 'pinepaper_shader_graph',
|
|
8014
|
+
annotations: {
|
|
8015
|
+
title: 'Shader Graph',
|
|
8016
|
+
readOnlyHint: false,
|
|
8017
|
+
destructiveHint: false,
|
|
8018
|
+
idempotentHint: false,
|
|
8019
|
+
openWorldHint: false,
|
|
8020
|
+
},
|
|
8021
|
+
description: `Build a pixel-processing graph from nodes (normal map, relighting, depth of field, bloom) and run it over an item or the whole scene.
|
|
8022
|
+
|
|
8023
|
+
ACTIONS:
|
|
8024
|
+
- node_types: the node vocabulary with each node's params. Call this first.
|
|
8025
|
+
- validate: { graph } — check it, store nothing. Errors are named (unknown type, duplicate id, dangling edge, cycle, branching).
|
|
8026
|
+
- create: { graph } → graphId — validate and store.
|
|
8027
|
+
- get / remove: { graphId }. list: the stored graphs.
|
|
8028
|
+
- apply: { target, graph | graphId, keepSource?, asItem? }
|
|
8029
|
+
target = an itemId → the item's pixels become a NEW image item in its place (returned itemId); the source is hidden unless keepSource:true. One undo step.
|
|
8030
|
+
target = 'scene' → the rendered frame is processed and returned as a PNG (data); the scene does not change. asItem:true adds it as an image item instead.
|
|
8031
|
+
|
|
8032
|
+
GRAPH: { nodes: [{ id, type, params? }], edges: [{ from, to }] } — a LINEAR chain from SourceInput to OutputCompositor. A NormalGen feeds a Relighting node; a DepthMap feeds DepthOfField.
|
|
8033
|
+
|
|
8034
|
+
PARAMS come from node_types, which is the studio's own list. SelectiveBloom: threshold 0..1, intensity 0..10, and radius 0..64 px (the glow's reach past the bright pixels; 0 keeps the glow inside them) where the studio has it. A glow can make the new item LARGER than its source, centred on it: read its bounds from the reply rather than reusing the source's.
|
|
8035
|
+
|
|
8036
|
+
The graph runs on the CPU. Stored graphs live for the session, not in the project. A studio without the shader graph refuses with the reason.
|
|
8037
|
+
|
|
8038
|
+
EXAMPLE: { action: 'apply', target: 'item_4', graph: { nodes: [{ id: 'in', type: 'SourceInput' }, { id: 'bloom', type: 'SelectiveBloom', params: { threshold: 0.6, intensity: 2 } }, { id: 'out', type: 'OutputCompositor' }], edges: [{ from: 'in', to: 'bloom' }, { from: 'bloom', to: 'out' }] } }`,
|
|
8039
|
+
inputSchema: {
|
|
8040
|
+
type: 'object',
|
|
8041
|
+
properties: {
|
|
8042
|
+
action: { type: 'string', enum: ['node_types', 'validate', 'create', 'get', 'list', 'remove', 'apply'], description: 'Shader graph operation' },
|
|
8043
|
+
graph: { type: 'object', description: 'validate / create / apply: {id?, nodes: [{id, type, params?}], edges: [{from, to}]}', additionalProperties: true },
|
|
8044
|
+
graphId: { type: 'string', description: 'get / remove / apply: a stored graph id' },
|
|
8045
|
+
target: { type: 'string', description: "apply: an itemId, or 'scene'" },
|
|
8046
|
+
keepSource: { type: 'boolean', description: 'apply to an item: keep the source visible' },
|
|
8047
|
+
asItem: { type: 'boolean', description: "apply to 'scene': add the result as an image item" },
|
|
8048
|
+
},
|
|
8049
|
+
required: ['action'],
|
|
8050
|
+
},
|
|
8051
|
+
},
|
|
8052
|
+
{
|
|
8053
|
+
name: 'pinepaper_place_on_surface',
|
|
8054
|
+
annotations: {
|
|
8055
|
+
title: 'Place on Surface',
|
|
8056
|
+
readOnlyHint: false,
|
|
8057
|
+
destructiveHint: false,
|
|
8058
|
+
idempotentHint: true,
|
|
8059
|
+
openWorldHint: false,
|
|
8060
|
+
},
|
|
8061
|
+
description: `Put a UI ONTO A SCREEN inside a photo: a phone, a monitor, a billboard. It is corner-pinned into the screen's perspective, matted by the photo's own brightness so a thumb or hand stays IN FRONT, softened to the photo, and keeps a little glass glare and light spill. A card laid over a phone photo reads as a sticker; this reads as the screen.
|
|
8062
|
+
|
|
8063
|
+
- photoId: the photo (an image item). sourceId: the UI, any canvas item or group (rendered at about twice the screen's size), or an image.
|
|
8064
|
+
- quad: the screen's corners TL, TR, BR, BL in the photo's pixels (quadSpace 'canvas' for canvas coordinates). Omit it to DETECT the screen (the largest bright, neutral region); the result's quad and detected say what was found. If nothing is found it refuses: pass quad.
|
|
8065
|
+
- The result is BAKED INTO THE PHOTO'S PIXELS, so a zoom or camera move on the photo carries it. Placing again in the same slot replaces it; use different slots for several screens. Undo restores the photo.
|
|
8066
|
+
- Tuning: occlusion ('luma' | 'none'), key, erode, matteBlur, soften, glare, spill, spillRadius, tint [r, g, b]. hideSource (default true) hides the UI item once placed.`,
|
|
8067
|
+
inputSchema: {
|
|
8068
|
+
type: 'object',
|
|
8069
|
+
properties: {
|
|
8070
|
+
photoId: { type: 'string', description: 'The photo (an image item).' },
|
|
8071
|
+
sourceId: { type: 'string', description: 'The UI to place (any item or group, or an image).' },
|
|
8072
|
+
quad: { type: 'array', items: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2 }, minItems: 4, maxItems: 4, description: 'Corners TL, TR, BR, BL as [x, y]; omit to detect.' },
|
|
8073
|
+
quadSpace: { type: 'string', enum: ['image', 'canvas'], description: "quad's coordinates (default 'image', the photo's pixels)." },
|
|
8074
|
+
occlusion: { type: 'string', enum: ['luma', 'none'], description: "'luma' (default) keeps a hand in front of the screen." },
|
|
8075
|
+
key: { type: 'number', description: 'Brightness that counts as screen (default: found from the photo).' },
|
|
8076
|
+
erode: { type: 'number', description: 'Pull the matte in from the bezel, px (default 2).' },
|
|
8077
|
+
matteBlur: { type: 'number', description: 'Matte edge softness (default 0.8).' },
|
|
8078
|
+
soften: { type: 'number', description: 'Soften the UI to the photo (default 0.5).' },
|
|
8079
|
+
glare: { type: 'number', description: 'Glass glare 0..1 (default 0.10).' },
|
|
8080
|
+
spill: { type: 'number', description: 'Light spill 0..1 (default 0.35).' },
|
|
8081
|
+
spillRadius: { type: 'number', description: 'Spill reach, px (default 40).' },
|
|
8082
|
+
tint: { type: 'array', items: { type: 'number' }, minItems: 3, maxItems: 3, description: 'Multiply the UI by [r, g, b] 0..1 to match white balance.' },
|
|
8083
|
+
slot: { type: 'string', description: "Screen name (default 'screen'); the same slot replaces." },
|
|
8084
|
+
hideSource: { type: 'boolean', description: 'Hide the UI item after placing (default true).' },
|
|
8085
|
+
},
|
|
8086
|
+
required: ['photoId', 'sourceId'],
|
|
8087
|
+
},
|
|
8088
|
+
},
|
|
8089
|
+
{
|
|
8090
|
+
name: 'pinepaper_import_scene',
|
|
8091
|
+
annotations: {
|
|
8092
|
+
title: 'Import Scene',
|
|
8093
|
+
readOnlyHint: false,
|
|
8094
|
+
destructiveHint: true,
|
|
8095
|
+
idempotentHint: true,
|
|
8096
|
+
openWorldHint: false,
|
|
8097
|
+
},
|
|
8098
|
+
description: `Restore a scene from a project document saved by pinepaper_export_scene { full: true }. REPLACES the current scene.
|
|
8099
|
+
|
|
8100
|
+
The engine validates the document before applying it: a truncated file, a foreign JSON blob or a document from a newer major version is refused, not half-applied. Returns counts (items, animated items, keyframes, relations): compare them with the export's to confirm nothing was lost.
|
|
8101
|
+
|
|
8102
|
+
- path: the file the export wrote (absolute, or relative to the server's working directory).
|
|
8103
|
+
- strict: false loads a document with validation problems anyway and returns them as warnings (default true: refuse).`,
|
|
8104
|
+
inputSchema: {
|
|
8105
|
+
type: 'object',
|
|
8106
|
+
properties: {
|
|
8107
|
+
path: { type: 'string', description: 'The project document file.' },
|
|
8108
|
+
strict: { type: 'boolean', description: 'Refuse a document with validation problems (default true).' },
|
|
8109
|
+
},
|
|
8110
|
+
required: ['path'],
|
|
7563
8111
|
},
|
|
7564
8112
|
},
|
|
7565
|
-
// ---------------------------------------------------------------------------
|
|
7566
|
-
// INFRASTRUCTURE: BROWSER CONTROL TOOLS
|
|
7567
|
-
// ---------------------------------------------------------------------------
|
|
7568
8113
|
{
|
|
7569
8114
|
name: 'pinepaper_browser_connect',
|
|
7570
8115
|
annotations: {
|
|
@@ -7818,9 +8363,9 @@ PRIVACY: No screenshots, no tool call arguments, no user content — only struct
|
|
|
7818
8363
|
|
|
7819
8364
|
⚠️ Call ONCE per pipeline. NEVER restart — creates duplicates.
|
|
7820
8365
|
|
|
7821
|
-
WORKFLOW: start_job →
|
|
8366
|
+
WORKFLOW: start_job → the craft tool(s) that fit the brief (styled_scene, story, character, text_style, compose, design_medium…) → batch_execute for the primitive glue, in ONE call → end_job (screenshot) → show user → iterate if needed. Primitive shapes over a generator backdrop is the stock look; use it only when no higher-level tool fits.
|
|
7822
8367
|
|
|
7823
|
-
RETURNS: canvasSize {width, height} — use this to position items within bounds. Also returns ontology: a pp: triples + item summary snapshot of the (typically empty) canvas. Reuse this baseline across the same agent turn instead of calling pinepaper_get_canvas_ontology unless you've mutated the canvas. When clearCanvas is false, the ontology captures whatever's already on the canvas.
|
|
8368
|
+
RETURNS: canvasSize {width, height} — use this to position items within bounds. Without a canvasPreset, an unbounded canvas is fixed at that size, so stills and video both export it (the result says so in note). Also returns ontology: a pp: triples + item summary snapshot of the (typically empty) canvas. Reuse this baseline across the same agent turn instead of calling pinepaper_get_canvas_ontology unless you've mutated the canvas. When clearCanvas is false, the ontology captures whatever's already on the canvas.
|
|
7824
8369
|
|
|
7825
8370
|
CANVAS PRESETS: instagram (1080x1080), instagram-story (1080x1920), tiktok (1080x1920), youtube (1920x1080), youtube-thumbnail (1280x720), twitter (1200x675), linkedin (1200x627), web, print-a4, print-letter, print-a4-landscape, print-letter-landscape (print presets carry 300 dpi, so a PDF comes out at paper size)`,
|
|
7826
8371
|
inputSchema: {
|
|
@@ -7971,7 +8516,7 @@ EXAMPLES:
|
|
|
7971
8516
|
idempotentHint: false,
|
|
7972
8517
|
openWorldHint: false,
|
|
7973
8518
|
},
|
|
7974
|
-
description: `Execute
|
|
8519
|
+
description: `Execute the primitive operations of a scene in a single call — canvas setup, backgrounds, items, animations, masks, effects, and playback. It is the GLUE: a styled scene, a character, a story or a crafted title has its own tool (styled_scene, character, story, text_style), called first.
|
|
7975
8520
|
|
|
7976
8521
|
NOTE: For diagrams (flowcharts, UML), use pinepaper_create_diagram_shape + pinepaper_connect instead.
|
|
7977
8522
|
|
|
@@ -7982,20 +8527,20 @@ NOTE: For diagrams (flowcharts, UML), use pinepaper_create_diagram_shape + pinep
|
|
|
7982
8527
|
ONTOLOGY PREFLIGHT: Before code generation, this tool validates itemType, relationType, effectType, and generatorName against the canonical PinePaper vocabulary. A typo (e.g. "circel") returns a structured VALIDATION_ERROR with the operation index, the offending field, the canonical valid list, and a suggested fix — fix and retry rather than guessing. Pass skipValidation: true to bypass when intentionally using experimental vocabulary.
|
|
7983
8528
|
|
|
7984
8529
|
WORKFLOW:
|
|
7985
|
-
1. pinepaper_agent_start_job → 2.
|
|
8530
|
+
1. pinepaper_agent_start_job → 2. the craft tools that fit the brief (styled_scene, story, character, text_style, compose…) → 3. pinepaper_agent_batch_execute (the primitive glue, in one call) → 4. pinepaper_agent_end_job (screenshot for validation)
|
|
7986
8531
|
After validation: user reviews screenshot → feedback → modify/recreate as needed.
|
|
7987
8532
|
|
|
7988
|
-
OPERATION TYPES (
|
|
8533
|
+
OPERATION TYPES (${AgentBatchOperationTypeSchema.options.length}) — use in this order:
|
|
7989
8534
|
|
|
7990
8535
|
CANVAS SETUP:
|
|
7991
|
-
set_canvas_size — {width, height} or {preset: "instagram"|"youtube"|"tiktok"|...}
|
|
8536
|
+
set_canvas_size — {width, height} or {preset: "instagram-post"|"instagram-story"|"youtube-thumbnail"|"tiktok"|"full-hd-1080p"|...} (studio preset keys, not platform names; an unknown one is refused with the list)
|
|
7992
8537
|
set_background — {backgroundColor: "#hex"}
|
|
7993
8538
|
execute_generator — {generatorName, generatorParams} → fills canvas with procedural art (paints WITHOUT fully registering items — see get_items; custom generator names need skipValidation:true)
|
|
7994
8539
|
Generators: drawBokeh, drawGradientMesh, drawWaves, drawSunburst, drawSunsetScene, drawGrid, drawCircuit, drawPattern, drawStackedCircles, drawGeometricAbstract, drawWindField, drawFluidFlow, drawOrganicFlow, drawNoiseTexture, drawGlobeWireframe
|
|
7995
8540
|
PineMath: drawFunctionPlot (y=f(x) plots), drawParametricCurve (x(t),y(t)), drawSimulation (pendulum, Lorenz, spring-mass), drawSpectrumAnalyzer (FFT), draw3DSurface (torus, sphere, Klein bottle)
|
|
7996
8541
|
|
|
7997
8542
|
ITEMS:
|
|
7998
|
-
create — {itemType, position: {x,y}, properties}
|
|
8543
|
+
create — {itemType, position: {x,y} or [x,y], properties}
|
|
7999
8544
|
itemType: text, circle, rectangle, star, triangle, polygon, ellipse, path, line, arc, pentagon, hexagon, diamond, arrow, heart
|
|
8000
8545
|
Properties per type:
|
|
8001
8546
|
text: {content, fontSize, fontFamily, color, fontWeight}
|
|
@@ -8037,14 +8582,14 @@ ANIMATION NOTES:
|
|
|
8037
8582
|
|
|
8038
8583
|
EXAMPLE — Animated sky scene with timed reveals:
|
|
8039
8584
|
{operations: [
|
|
8040
|
-
{type: "set_canvas_size", preset: "
|
|
8585
|
+
{type: "set_canvas_size", preset: "full-hd-1080p"},
|
|
8041
8586
|
{type: "execute_generator", generatorName: "drawGradientMesh", generatorParams: {colors: ["#1e3a5f", "#87CEEB", "#fbbf24"], bgColor: "#0c1445", blobCount: 4}},
|
|
8042
8587
|
{type: "create", itemType: "circle", position: {x: 1600, y: 150}, properties: {radius: 80, color: "#fbbf24", shadowColor: "#fbbf24", shadowBlur: 40}},
|
|
8043
8588
|
{type: "create", itemType: "ellipse", position: {x: 400, y: 180}, properties: {radiusX: 100, radiusY: 35, color: "#ffffff", opacity: 0.8}},
|
|
8044
8589
|
{type: "create", itemType: "ellipse", position: {x: 900, y: 120}, properties: {radiusX: 120, radiusY: 40, color: "#ffffff", opacity: 0.7}},
|
|
8045
8590
|
{type: "create", itemType: "text", position: {x: 960, y: 500}, properties: {content: "Dream Big", fontSize: 96, color: "#ffffff", fontWeight: "bold"}},
|
|
8046
8591
|
{type: "animate", itemId: "$0", animationType: "pulse", animationOptions: {speed: 0.3}},
|
|
8047
|
-
{type: "animate", itemId: "$1", animationType: "
|
|
8592
|
+
{type: "animate", itemId: "$1", animationType: "slideLeftRight", animationOptions: {speed: 0.2}},
|
|
8048
8593
|
{type: "animate", itemId: "$2", animationType: "slide", animationOptions: {speed: 0.15, direction: "left"}},
|
|
8049
8594
|
{type: "keyframe_animate", itemId: "$3", keyframes: [{time: 0, properties: {opacity: 0, scale: 0.8}}, {time: 2, properties: {opacity: 1, scale: 1}, easing: "easeOutBack"}], duration: 3},
|
|
8050
8595
|
{type: "apply_mask", itemId: "$3", maskPreset: "wipeLeft"},
|
|
@@ -8081,9 +8626,11 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
8081
8626
|
description: 'For create: item type',
|
|
8082
8627
|
},
|
|
8083
8628
|
position: {
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8629
|
+
oneOf: [
|
|
8630
|
+
{ type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } } },
|
|
8631
|
+
{ type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2 },
|
|
8632
|
+
],
|
|
8633
|
+
description: 'For create: position {x, y} or [x, y]',
|
|
8087
8634
|
},
|
|
8088
8635
|
properties: { type: 'object', description: 'For create/modify: item properties' },
|
|
8089
8636
|
itemId: { type: 'string', description: 'Target item ID or $N reference' },
|
|
@@ -8093,7 +8640,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
8093
8640
|
// Loop animation
|
|
8094
8641
|
animationType: {
|
|
8095
8642
|
type: 'string',
|
|
8096
|
-
enum: [
|
|
8643
|
+
enum: [...SimpleAnimationTypeSchema.options],
|
|
8097
8644
|
description: 'For animate: loop animation type',
|
|
8098
8645
|
},
|
|
8099
8646
|
animationOptions: { type: 'object', description: 'For animate: {speed, amplitude, direction}' },
|
|
@@ -8117,7 +8664,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
8117
8664
|
targetId: { type: 'string', description: 'For relation: target item ID or $N' },
|
|
8118
8665
|
relationType: {
|
|
8119
8666
|
type: 'string',
|
|
8120
|
-
enum: [
|
|
8667
|
+
enum: [...RelationTypeSchema.options],
|
|
8121
8668
|
description: 'For relation: type',
|
|
8122
8669
|
},
|
|
8123
8670
|
relationOptions: { type: 'object', description: 'For relation: options' },
|
|
@@ -8132,7 +8679,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
8132
8679
|
// Effects
|
|
8133
8680
|
effectType: {
|
|
8134
8681
|
type: 'string',
|
|
8135
|
-
enum: [
|
|
8682
|
+
enum: [...EffectTypeSchema.options],
|
|
8136
8683
|
description: 'For apply_effect: effect type',
|
|
8137
8684
|
},
|
|
8138
8685
|
effectParams: { type: 'object', description: 'For apply_effect: parameters' },
|
|
@@ -8207,7 +8754,7 @@ Held exports last until released, or until a LATER export needs the space — an
|
|
|
8207
8754
|
{
|
|
8208
8755
|
name: 'pinepaper_agent_export',
|
|
8209
8756
|
annotations: {
|
|
8210
|
-
title: 'Export
|
|
8757
|
+
title: 'Export Video, Stills, Animation, Ads, Captions or Audio',
|
|
8211
8758
|
readOnlyHint: true,
|
|
8212
8759
|
destructiveHint: false,
|
|
8213
8760
|
idempotentHint: true,
|
|
@@ -8236,7 +8783,7 @@ PLATFORMS & OPTIMAL FORMATS:
|
|
|
8236
8783
|
|
|
8237
8784
|
A preset's dimensions assume the canvas has the same aspect. When it does not, the canvas aspect is KEPT (never stretched) and its short edge takes the preset's short edge — a 4:5 canvas on instagram exports 1080x1350 — and the result carries platformFit saying so.
|
|
8238
8785
|
|
|
8239
|
-
FORMATS:
|
|
8786
|
+
FORMATS: ${AgentExportFormatSchema.options.join(', ')}.
|
|
8240
8787
|
QUALITY: draft, standard, high.
|
|
8241
8788
|
FRAMING: canvas (default, full canvas) | camera (camera_animates first-keyframe viewport — video formats only, requires a keyframe-mode walkthrough on the canvas; camera animation still drives motion within the fixed output frame).
|
|
8242
8789
|
|
|
@@ -8259,7 +8806,7 @@ VERIFY MOTION BEFORE YOU RENDER. An export takes seconds to minutes and shows yo
|
|
|
8259
8806
|
platform: {
|
|
8260
8807
|
type: 'string',
|
|
8261
8808
|
enum: ['auto', 'instagram', 'instagram-story', 'tiktok', 'youtube', 'youtube-thumbnail', 'twitter', 'linkedin', 'web', 'print-a4', 'print-letter', 'print-a4-landscape', 'print-letter-landscape'],
|
|
8262
|
-
description: "Target platform, which sets the output DIMENSIONS. 'auto' (the default) renders the canvas at its own size — use it when the canvas is already the size you want, otherwise a preset
|
|
8809
|
+
description: "Target platform, which sets the output DIMENSIONS. 'auto' (the default) renders the canvas at its own size — use it when the canvas is already the size you want, otherwise a preset sets the output SIZE from the platform's short edge. The canvas aspect is kept, never stretched: on a canvas of a different aspect the output is not the platform's frame (an 800x600 canvas on instagram exports 1440x1080), and the result's platformFit says so. To get the exact frame, set the canvas to the platform's aspect first.",
|
|
8263
8810
|
},
|
|
8264
8811
|
format: {
|
|
8265
8812
|
type: 'string',
|
|
@@ -8675,6 +9222,14 @@ Three-phase analysis:
|
|
|
8675
9222
|
|
|
8676
9223
|
Quality tiers: basic (<0.4), fair (0.4-0.6), good (0.6-0.8), excellent (>=0.8)
|
|
8677
9224
|
|
|
9225
|
+
DEFINITION SHAPE (a template document; id, name, category and data are REQUIRED, the rest optional; items and relations sit under data):
|
|
9226
|
+
{ id, name, category, dimensions?: { width, height }, duration? (s), description?, tags?: [..], semantics?: {..},
|
|
9227
|
+
data: {
|
|
9228
|
+
items: [{ id, type ('circle', 'text' …), x?, y? | position?: [x, y], content?, animationType?, keyframes?: [{ time, properties: {..}, easing? }], mask?: { type } }],
|
|
9229
|
+
relations: [{ type ('orbits' …), source | from, target | to, params? }],
|
|
9230
|
+
backgroundGenerator?, backgroundGeneratorParams?
|
|
9231
|
+
} }
|
|
9232
|
+
|
|
8678
9233
|
USE WHEN:
|
|
8679
9234
|
- Checking a template for structural issues before publishing
|
|
8680
9235
|
- Comparing quality scores between template variants
|
|
@@ -8860,12 +9415,12 @@ USE WHEN:
|
|
|
8860
9415
|
- You want more/less detail in tool descriptions → switch verbosity
|
|
8861
9416
|
|
|
8862
9417
|
PROFILES:
|
|
8863
|
-
- full:
|
|
8864
|
-
- agent:
|
|
9418
|
+
- full: Every tool, no filtering
|
|
9419
|
+
- agent: Every tool except the diagnostics (get_performance_metrics, diagnostic_report) and register_item — default
|
|
8865
9420
|
- diagram: Diagram/flowchart tools
|
|
8866
9421
|
- map: Map/choropleth tools
|
|
8867
9422
|
- font: Font creation tools
|
|
8868
|
-
- minimal: Bare essentials
|
|
9423
|
+
- minimal: Bare essentials
|
|
8869
9424
|
|
|
8870
9425
|
VERBOSITY:
|
|
8871
9426
|
- verbose: Full descriptions
|
|
@@ -9091,7 +9646,7 @@ ACTIONS:
|
|
|
9091
9646
|
- set_rulers: Show/hide rulers. Params: enabled (bool)
|
|
9092
9647
|
- set_grid: Show/hide unit grid. Params: enabled (bool)
|
|
9093
9648
|
- get_dimensions: Get item bounds. Params: itemId → returns {x, y, width, height, rotation}
|
|
9094
|
-
- set_snap: Enable/disable
|
|
9649
|
+
- set_snap: Enable/disable snapping a DRAG or RESIZE in the studio to the unit grid (only while the grid is on). Params: enabled (bool). Items placed by tools are not snapped.`,
|
|
9095
9650
|
inputSchema: {
|
|
9096
9651
|
type: 'object',
|
|
9097
9652
|
properties: {
|