@pinepaper.studio/mcp-server 1.6.17 → 1.6.19
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 +189 -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 +86 -1
- package/dist/browser/puppeteer-controller.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.js +274 -6
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/build-info.json +2 -2
- 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 +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +94 -16
- 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/ontology-validator.d.ts.map +1 -1
- package/dist/ontology/ontology-validator.js +9 -5
- package/dist/ontology/ontology-validator.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/tools/compact-descriptions.d.ts.map +1 -1
- package/dist/tools/compact-descriptions.js +15 -14
- package/dist/tools/compact-descriptions.js.map +1 -1
- package/dist/tools/definitions.d.ts +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +556 -157
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-lists.d.ts +35 -0
- package/dist/tools/engine-lists.d.ts.map +1 -0
- package/dist/tools/engine-lists.js +186 -0
- package/dist/tools/engine-lists.js.map +1 -0
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +18 -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 +66 -9
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/handlers/brand-intake.d.ts +95 -0
- package/dist/tools/handlers/brand-intake.d.ts.map +1 -0
- package/dist/tools/handlers/brand-intake.js +242 -0
- package/dist/tools/handlers/brand-intake.js.map +1 -0
- 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 +77 -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 -11
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +462 -43
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +36 -29
- 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 +5 -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 +5 -0
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +96 -4
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +1265 -277
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +4181 -1757
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +378 -84
- package/dist/types/schemas.js.map +1 -1
- package/dist/utils/mcp-log.d.ts +16 -0
- package/dist/utils/mcp-log.d.ts.map +1 -0
- package/dist/utils/mcp-log.js +10 -0
- package/dist/utils/mcp-log.js.map +1 -0
- package/dist/utils/progress.d.ts +28 -0
- package/dist/utils/progress.d.ts.map +1 -0
- package/dist/utils/progress.js +24 -0
- package/dist/utils/progress.js.map +1 -0
- package/dist/vendor/design/design-style.js +1 -1
- package/manifest.json +22 -2
- package/package.json +7 -4
|
@@ -10,8 +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 {
|
|
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';
|
|
15
16
|
/**
|
|
16
17
|
* Vocabulary lists derived from Zod schemas so tool descriptions stay in
|
|
17
18
|
* sync automatically. The description-schema-parity test
|
|
@@ -25,6 +26,7 @@ const SIMPLE_ANIMATION_TYPES_LIST = SimpleAnimationTypeSchema.options.join(', ')
|
|
|
25
26
|
// easeInCubic / easeOutCubic / easeOutBounce, which keyframe_animate rejects
|
|
26
27
|
// (round 8 DD, 1.68) — derived now, so it cannot say what the schema refuses.
|
|
27
28
|
const KEYFRAME_EASINGS_LIST = EasingSchema.options.join(', ');
|
|
29
|
+
const EASING_LINES = KEYFRAME_EASINGS.map((e) => `- ${e}: ${EASING_DESCRIPTIONS[e]}`).join('\n');
|
|
28
30
|
// =============================================================================
|
|
29
31
|
// AI AGENT GUIDE (exported for pinepaper_tool_guide)
|
|
30
32
|
// =============================================================================
|
|
@@ -109,7 +111,7 @@ Text: text (content, fontSize, fontFamily, color, fontWeight)
|
|
|
109
111
|
All items: opacity, shadowColor, shadowBlur, blendMode, strokeColor, strokeWidth
|
|
110
112
|
|
|
111
113
|
SVG imports for recognizable objects (planes, cars, animals, buildings):
|
|
112
|
-
pinepaper_search_assets → pinepaper_import_asset (
|
|
114
|
+
pinepaper_search_assets → pinepaper_import_asset (open SVG icon repositories)
|
|
113
115
|
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.
|
|
114
116
|
|
|
115
117
|
Photos / raster images (on-device, no upload):
|
|
@@ -195,7 +197,7 @@ Measurement: pinepaper_measurement — rulers, grid, snap-to-grid, get item dime
|
|
|
195
197
|
|
|
196
198
|
─── CANVAS & EXPORT ───
|
|
197
199
|
|
|
198
|
-
|
|
200
|
+
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.
|
|
199
201
|
Export: pinepaper_agent_export (SVG/PNG/GIF/MP4/WebM/PDF, plus WAV for the soundtrack alone), pinepaper_export_svg
|
|
200
202
|
Widget: pinepaper_export_widget (pp:PinePaper ontology JSON), pinepaper_export_widget_html (self-contained HTML with tree-shaken runtime)`;
|
|
201
203
|
/*
|
|
@@ -253,13 +255,11 @@ USE WHEN:
|
|
|
253
255
|
- Custom canvas size requirements
|
|
254
256
|
- Design elements extend beyond current canvas bounds
|
|
255
257
|
|
|
256
|
-
COMMON PRESETS:
|
|
257
|
-
- instagram-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
-
|
|
261
|
-
- hd-landscape: 1920x1080
|
|
262
|
-
- hd-portrait: 1080x1920
|
|
258
|
+
COMMON PRESETS (studio keys; an unknown key is refused with the full list):
|
|
259
|
+
- instagram-post: 1080x1080 · instagram-portrait · instagram-story: 1080x1920
|
|
260
|
+
- youtube-thumbnail: 1280x720 · youtube-short · tiktok: 1080x1920
|
|
261
|
+
- twitter-post: 1200x675 · facebook-post · linkedin-post · pinterest-pin
|
|
262
|
+
- full-hd-1080p: 1920x1080 · hd-720p: 1280x720 · a4-portrait / a4-landscape and other print sizes
|
|
263
263
|
|
|
264
264
|
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.`,
|
|
265
265
|
inputSchema: {
|
|
@@ -269,10 +269,10 @@ Max canvas size: 16384 per side (a 12000 px DOOH or projection canvas fits); abo
|
|
|
269
269
|
height: { type: 'number', description: 'Canvas height in pixels (100-16384)' },
|
|
270
270
|
preset: {
|
|
271
271
|
type: 'string',
|
|
272
|
-
description:
|
|
272
|
+
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.",
|
|
273
273
|
},
|
|
274
274
|
},
|
|
275
|
-
|
|
275
|
+
// width + height, or preset alone (enforced by the Zod schema).
|
|
276
276
|
},
|
|
277
277
|
},
|
|
278
278
|
{
|
|
@@ -1104,7 +1104,7 @@ NOTE: Import-only — there is no MCP equivalent of FxTool's exportMermaid. Merm
|
|
|
1104
1104
|
idempotentHint: false,
|
|
1105
1105
|
openWorldHint: true,
|
|
1106
1106
|
},
|
|
1107
|
-
description: `Import a raster image from a URL
|
|
1107
|
+
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.
|
|
1108
1108
|
|
|
1109
1109
|
USE WHEN:
|
|
1110
1110
|
- User wants to add a photo, PNG, JPG, or other raster image
|
|
@@ -1126,6 +1126,9 @@ Apply a mask shape during import to clip the image:
|
|
|
1126
1126
|
|
|
1127
1127
|
SIZE CONSTRAINTS:
|
|
1128
1128
|
Use maxWidth/maxHeight to constrain the image dimensions while preserving aspect ratio.
|
|
1129
|
+
- 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.
|
|
1130
|
+
- nativeSize: true places the image at its source footprint even if it was stored smaller.
|
|
1131
|
+
- smoothing: 'off' (nearest-neighbour, for pixel art) | 'low' (default) | 'medium' | 'high'.
|
|
1129
1132
|
|
|
1130
1133
|
EXAMPLES:
|
|
1131
1134
|
- Import photo: url: "https://example.com/photo.jpg"
|
|
@@ -1182,7 +1185,7 @@ EXAMPLES:
|
|
|
1182
1185
|
idempotentHint: true,
|
|
1183
1186
|
openWorldHint: true,
|
|
1184
1187
|
},
|
|
1185
|
-
description: `Search for free SVG assets from open repositories (
|
|
1188
|
+
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.
|
|
1186
1189
|
|
|
1187
1190
|
USE WHEN:
|
|
1188
1191
|
- User wants icons, illustrations, or clipart
|
|
@@ -1192,15 +1195,13 @@ USE WHEN:
|
|
|
1192
1195
|
- Need graphics that are too complex to draw with paths
|
|
1193
1196
|
|
|
1194
1197
|
REPOSITORIES:
|
|
1195
|
-
- SVGRepo: 500,000+ icons, various categories and licenses
|
|
1196
|
-
- OpenClipart: 150,000+ public domain clipart
|
|
1197
1198
|
- Iconify: 200,000+ icon sets (various licenses)
|
|
1198
1199
|
- Font Awesome: 2,000+ free icons (CC BY 4.0)
|
|
1199
1200
|
|
|
1200
1201
|
WORKFLOW:
|
|
1201
1202
|
1. Search for assets: pinepaper_search_assets(query: "rocket")
|
|
1202
1203
|
2. Review results (you'll see assetIds, previews, licenses)
|
|
1203
|
-
3. Import chosen asset: pinepaper_import_asset(assetId: "
|
|
1204
|
+
3. Import chosen asset: pinepaper_import_asset(assetId: "iconify_mdi_rocket")
|
|
1204
1205
|
|
|
1205
1206
|
IMPORTANT NOTES:
|
|
1206
1207
|
- Always respect license terms (check license field in results)
|
|
@@ -1237,7 +1238,7 @@ SVG CONTENT RETRIEVAL:
|
|
|
1237
1238
|
},
|
|
1238
1239
|
repository: {
|
|
1239
1240
|
type: 'string',
|
|
1240
|
-
enum: ['all', '
|
|
1241
|
+
enum: ['all', 'iconify', 'fontawesome'],
|
|
1241
1242
|
description: 'Which repository to search (default: all)',
|
|
1242
1243
|
},
|
|
1243
1244
|
limit: {
|
|
@@ -1271,12 +1272,12 @@ USE WHEN:
|
|
|
1271
1272
|
WORKFLOW:
|
|
1272
1273
|
1. Search: pinepaper_search_assets(query: "rocket")
|
|
1273
1274
|
2. Choose: Review results, pick one with desired license
|
|
1274
|
-
3. Import: pinepaper_import_asset(assetId: "
|
|
1275
|
+
3. Import: pinepaper_import_asset(assetId: "iconify_mdi_rocket", position: {x: 400, y: 300})
|
|
1275
1276
|
4. Modify: Use pinepaper_modify_item to adjust size, color, etc.
|
|
1276
1277
|
5. Animate: Use pinepaper_animate or pinepaper_add_relation for motion
|
|
1277
1278
|
|
|
1278
1279
|
PARAMETERS:
|
|
1279
|
-
- assetId: From search results (e.g., "
|
|
1280
|
+
- assetId: From search results (e.g., "iconify_mdi_rocket")
|
|
1280
1281
|
- url: Alternative to assetId - direct URL to SVG
|
|
1281
1282
|
- position: Where to place on canvas (default: center)
|
|
1282
1283
|
- scale: Size multiplier (default: 1.0)
|
|
@@ -1295,9 +1296,9 @@ CACHING:
|
|
|
1295
1296
|
- No disk storage (memory only)
|
|
1296
1297
|
|
|
1297
1298
|
EXAMPLES:
|
|
1298
|
-
- Import from search: {assetId: "
|
|
1299
|
+
- Import from search: {assetId: "iconify_mdi_rocket", position: {x: 400, y: 300}, scale: 2.0}
|
|
1299
1300
|
- Import from URL: {url: "https://example.com/icon.svg", position: {x: 500, y: 400}}
|
|
1300
|
-
- Import with color override: {assetId: "
|
|
1301
|
+
- Import with color override: {assetId: "iconify_mdi_heart", position: {x: 400, y: 300}, color: "#ef4444"}
|
|
1301
1302
|
|
|
1302
1303
|
RETURNS:
|
|
1303
1304
|
- itemId: ID of the imported SVG group (use for further modifications)
|
|
@@ -1307,7 +1308,7 @@ RETURNS:
|
|
|
1307
1308
|
properties: {
|
|
1308
1309
|
assetId: {
|
|
1309
1310
|
type: 'string',
|
|
1310
|
-
description: 'Asset ID from search results (e.g., "
|
|
1311
|
+
description: 'Asset ID from search results (e.g., "iconify_mdi_rocket")',
|
|
1311
1312
|
},
|
|
1312
1313
|
url: {
|
|
1313
1314
|
type: 'string',
|
|
@@ -1738,6 +1739,8 @@ EXAMPLES:
|
|
|
1738
1739
|
annotations: { title: 'Beat-Snapped Cuts', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
1739
1740
|
description: `Cut on the music: analyse a track's beats (or take beat times you already have), fit a beat grid, and return cut points on every beat, every bar, or every N beats — optionally splitting a video or audio clip at each one.
|
|
1740
1741
|
|
|
1742
|
+
The song is the backbone of the edit: when the music is generated, cut the picture to its beats and bars and use the whole song. Do not trim the song to fit shots that were planned first.
|
|
1743
|
+
|
|
1741
1744
|
- source: the music — an upload id, URL, data: URL or local file path. Or beats: [seconds] to skip the analysis.
|
|
1742
1745
|
- every: 'beat' (default) | 'bar' | N beats. beatsPerBar (default 4). range: [from, to] seconds.
|
|
1743
1746
|
- bpm: the tempo, if you know it (the grid's phase is still fitted to the music). Otherwise the studio's detection and its usual confusions (double, half, 3:4, 4:3 …) are scored against the music and the best is used; bpmHint: [min, max] keeps the choice in a range. The result lists the candidates, and says lowConfidence with a warning when the tempo is uncertain — pass bpm then.
|
|
@@ -1805,6 +1808,12 @@ EXAMPLE: { checks: ["contrast", "flash"], duration: 6, fps: 20 }`,
|
|
|
1805
1808
|
annotations: { title: 'Generate Image (cloud)', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
1806
1809
|
description: `Generate images with an AI model in the PinePaper cloud, and optionally place the first one on the canvas. CHARGED per output — price it first with pinepaper_generate_estimate (free).
|
|
1807
1810
|
|
|
1811
|
+
PLAN FIRST; GENERATE ONLY WHAT THE EDIT USES. Every output is paid in full whether or not it is used, so generate-then-trim is waste:
|
|
1812
|
+
- Plan the whole timeline (shots, lengths, roles) before the first paid call, and price the whole plan with pinepaper_generate_estimate.
|
|
1813
|
+
- Reuse before generating: an image you already generated is referenced by its ref "/scene-assets/<id>".
|
|
1814
|
+
- Video: choose each shot's length from the lengths the model allows (listModels; Veo takes 4, 6 or 8 s) and re-time the edit to them. Do not generate long and crop.
|
|
1815
|
+
- Music: the song is the backbone. Its length only loosely follows the request, so cut the picture to its beats and bars with pinepaper_beat_cuts and use the whole song, rather than trimming it to fit the picture.
|
|
1816
|
+
|
|
1808
1817
|
Needs a service-account key in PINEPAPER_API_KEY (pp_sa_…, render:create scope). It is an internal beta enabled per organisation: without it the result says "not enabled for your organisation", and nothing is charged. Every refusal says whether anything was charged.
|
|
1809
1818
|
|
|
1810
1819
|
- model or useCase: a model id from pinepaper_generate_estimate {listModels: true}, or a use case and the cloud picks its default (hero stills, plates, text in the image, character-consistent edits).
|
|
@@ -1847,7 +1856,7 @@ The result: assets [{assetId, ref, width, height}] — the sizes the files HAVE
|
|
|
1847
1856
|
{
|
|
1848
1857
|
name: 'pinepaper_generate_estimate',
|
|
1849
1858
|
annotations: { title: 'Estimate Generation (free)', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1850
|
-
description: `Free: price a pinepaper_generate call before making it, or list the models. { listModels: true } returns every model you may use with its price per output, use cases, licence notes and the defaults per use case; { model | useCase, input } returns the estimate for that request. Needs PINEPAPER_API_KEY, like pinepaper_generate.`,
|
|
1859
|
+
description: `Free: price a pinepaper_generate call before making it, or list the models. Estimate the WHOLE plan (every asset the edit needs, at the lengths it will use) before generating any of it, not call by call. { listModels: true } returns every model you may use with its price per output, use cases, licence notes and the defaults per use case; { model | useCase, input } returns the estimate for that request. Needs PINEPAPER_API_KEY, like pinepaper_generate.`,
|
|
1851
1860
|
inputSchema: {
|
|
1852
1861
|
type: 'object',
|
|
1853
1862
|
properties: {
|
|
@@ -2107,7 +2116,7 @@ MODES:
|
|
|
2107
2116
|
|
|
2108
2117
|
This complements pinepaper_validate_design (which validates a definition object, not the live scene).
|
|
2109
2118
|
|
|
2110
|
-
EXAMPLE (pre-flight): { ops: [ { kind: "create", id: "c1", itemType: "circle" }, { kind: "addRelation",
|
|
2119
|
+
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)`,
|
|
2111
2120
|
inputSchema: {
|
|
2112
2121
|
type: 'object',
|
|
2113
2122
|
properties: {
|
|
@@ -2135,6 +2144,13 @@ By default returns a cheap per-frame hash + byte size (token-light), plus unique
|
|
|
2135
2144
|
|
|
2136
2145
|
Set includeDataUrls: true to also get each frame as a PNG data URL (large — only for a few frames).
|
|
2137
2146
|
|
|
2147
|
+
LOOK AT YOUR OWN FILM — sheet: one tiled image of many frames, saved to a file (filePath), plus a review rubric to score it against. Structure checks cannot see what a frame looks like; this can. Pick the frames with times (e.g. the music's beat times), or:
|
|
2148
|
+
- sheet.every + sheet.duration: one frame every N seconds.
|
|
2149
|
+
- sheet.strip {at, count?, step?}: consecutive frames around a moment (a transition, a stutter).
|
|
2150
|
+
- sheet.loopSeam + sheet.duration: the last frame beside the first.
|
|
2151
|
+
tileWidth defaults to 360 px, phone width: if a word cannot be read in its tile, it cannot be read on a phone. Then open the file, score review.criteria 1-10, and go through review.checklist.
|
|
2152
|
+
EXAMPLE: { sheet: { every: 1, duration: 15 } } → one ~15-tile JPEG.
|
|
2153
|
+
|
|
2138
2154
|
USE WHEN:
|
|
2139
2155
|
- Verifying an animation is deterministic / reproducible
|
|
2140
2156
|
- Checking whether a scene actually animates across a time range
|
|
@@ -2157,8 +2173,26 @@ EXAMPLE: { times: [0, 0.5, 1, 1.5, 2], seed: 42 }`,
|
|
|
2157
2173
|
type: 'boolean',
|
|
2158
2174
|
description: 'Include each frame as a PNG data URL (large). Default false → hashes only.',
|
|
2159
2175
|
},
|
|
2176
|
+
sheet: {
|
|
2177
|
+
type: 'object',
|
|
2178
|
+
description: 'Return ONE tiled image saved to a file, plus a critique rubric, instead of per-frame hashes. times is then optional.',
|
|
2179
|
+
properties: {
|
|
2180
|
+
every: { type: 'number', description: 'One frame every N seconds from 0 to duration.' },
|
|
2181
|
+
duration: { type: 'number', description: 'Scene length in seconds (for every and loopSeam).' },
|
|
2182
|
+
strip: {
|
|
2183
|
+
type: 'object',
|
|
2184
|
+
properties: { at: { type: 'number' }, count: { type: 'integer', minimum: 2, maximum: 24 }, step: { type: 'number' } },
|
|
2185
|
+
required: ['at'],
|
|
2186
|
+
description: 'Consecutive frames around a moment: count (default 8), step seconds apart (default 1/30).',
|
|
2187
|
+
},
|
|
2188
|
+
loopSeam: { type: 'boolean', description: 'The last frame beside the first.' },
|
|
2189
|
+
tileWidth: { type: 'integer', minimum: 120, maximum: 1080, description: 'Tile width in px (default 360, phone width).' },
|
|
2190
|
+
columns: { type: 'integer', minimum: 1, maximum: 12 },
|
|
2191
|
+
format: { type: 'string', enum: ['jpeg', 'png'], description: 'Default jpeg (small).' },
|
|
2192
|
+
labels: { type: 'boolean', description: 'Stamp each tile with its time (default true).' },
|
|
2193
|
+
},
|
|
2194
|
+
},
|
|
2160
2195
|
},
|
|
2161
|
-
required: ['times'],
|
|
2162
2196
|
},
|
|
2163
2197
|
},
|
|
2164
2198
|
{
|
|
@@ -2181,7 +2215,7 @@ ACTIONS:
|
|
|
2181
2215
|
- 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.
|
|
2182
2216
|
- 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.
|
|
2183
2217
|
- 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.
|
|
2184
|
-
- For an IMAGE on the timeline,
|
|
2218
|
+
- For an IMAGE on the timeline, import it with pinepaper_import_image, then give it bornAt / ttl (seconds on screen) with pinepaper_modify_item.
|
|
2185
2219
|
|
|
2186
2220
|
NOTES:
|
|
2187
2221
|
- 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.
|
|
@@ -2194,11 +2228,15 @@ VIDEO EDITING:
|
|
|
2194
2228
|
- apply_track_matte: { id, matteItemId, channel? (luma|alpha|red), invert?, strength?, hideMatte?, live? } — the matte item's pixels drive the target's alpha. The classic: a headline filled with footage. live: true keeps the cut tracking an ANIMATING matte (kinetic mask reveal); otherwise it bakes once, destructively (recorded in data.filterChain for round-trip).
|
|
2195
2229
|
- stop_live_matte: { id } — stop a live matte, keep the last cut.
|
|
2196
2230
|
|
|
2231
|
+
MAKE ONE LIKE THIS — a reference video's FORMAT, never its content:
|
|
2232
|
+
- analyze_reference: { id, thumbnails? } — the uploaded reference clip (upload_video it first) → cuts (to the frame), shots (length, colours), pacing, beat, and card: the same facts as plain text. Read the card before planning.
|
|
2233
|
+
- remake_from_reference: { texts, duration?, clear? } — builds a new piece on the last analysed reference: one card per shot, cut at its cut times, in each shot's colours, the headline popping on its beats. texts are YOUR words, one per shot. Nothing of the footage, its words or its sound is copied; the reference clip stays in the project muted. One undo step.
|
|
2234
|
+
|
|
2197
2235
|
EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, timeOffset: 1 }`,
|
|
2198
2236
|
inputSchema: {
|
|
2199
2237
|
type: 'object',
|
|
2200
2238
|
properties: {
|
|
2201
|
-
action: { type: 'string', enum: ['upload_video', 'upload_audio', 'list', 'remove', 'set_playback_rate', 'set_clip', 'split', 'add_transition', 'set_time_remap', 'speed_ramp', 'match_cut', 'apply_track_matte', 'stop_live_matte'], description: "Media action" },
|
|
2239
|
+
action: { type: 'string', enum: ['upload_video', 'upload_audio', 'list', 'remove', 'set_playback_rate', 'set_clip', 'split', 'add_transition', 'set_time_remap', 'speed_ramp', 'match_cut', 'apply_track_matte', 'stop_live_matte', 'analyze_reference', 'remake_from_reference'], description: "Media action" },
|
|
2202
2240
|
transition: { type: 'object', properties: { type: { type: 'string', enum: ['crossfade', 'dip'] }, seconds: { type: 'number', exclusiveMinimum: 0, maximum: 10 }, color: { type: 'string' } }, description: "add_transition: { type ('crossfade' default | 'dip'), seconds, color (dip) }." },
|
|
2203
2241
|
atPlayhead: { type: 'boolean', description: 'upload_video / upload_audio: start at the current playback time (instead of timeOffset).' },
|
|
2204
2242
|
remapTrack: { type: 'array', items: { type: 'object', properties: { time: { type: 'number' }, value: { type: 'number' }, easing: { type: 'string' } }, required: ['time', 'value'] }, description: 'set_time_remap: canvas-seconds → source-seconds curve (≥2 points). null clears the remap.' },
|
|
@@ -2218,7 +2256,11 @@ EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, time
|
|
|
2218
2256
|
hideMatte: { type: 'boolean', description: 'apply_track_matte: hide the matte item after applying.' },
|
|
2219
2257
|
live: { type: 'boolean', description: 'apply_track_matte: keep re-cutting as the matte moves/animates — the kinetic mask reveal. Omitted = bake once, destructively.' },
|
|
2220
2258
|
url: { type: 'string', description: "Required for upload_video / upload_audio: an http(s) URL, a data: URL, or a PATH TO A LOCAL FILE. Remote URLs and local files are fetched/read by the server (the page may not fetch third-party hosts), so stock music and SFX links work; the HTTP status or content-type is named if one does not. Up to 48 MB." },
|
|
2221
|
-
id: { type: 'string', description: 'Media id — required for remove / set_playback_rate / set_clip.' },
|
|
2259
|
+
id: { type: 'string', description: 'Media id — required for remove / set_playback_rate / set_clip / analyze_reference.' },
|
|
2260
|
+
texts: { type: 'array', items: { type: 'string' }, description: 'remake_from_reference: your words, one per shot of the reference.' },
|
|
2261
|
+
duration: { type: 'number', description: 'remake_from_reference: total seconds (default the reference\'s own).' },
|
|
2262
|
+
clear: { type: 'boolean', description: 'remake_from_reference: empty the canvas first (media survives).' },
|
|
2263
|
+
thumbnails: { type: 'boolean', description: 'analyze_reference: include a small image per shot (default false; token-heavy).' },
|
|
2222
2264
|
rate: { type: 'number', description: 'Playback rate 0.25–4 — set_playback_rate.' },
|
|
2223
2265
|
inPoint: { type: 'number', description: 'Clip in-point (media-time s) — set_clip.' },
|
|
2224
2266
|
outPoint: { type: 'number', description: 'Clip out-point (media-time s, > inPoint) — set_clip.' },
|
|
@@ -2250,6 +2292,10 @@ ACTIONS:
|
|
|
2250
2292
|
- 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.
|
|
2251
2293
|
- 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.
|
|
2252
2294
|
- list_styles: {} — styles + palettes + axes, for pickers. Call this first; styleKey strings come from here.
|
|
2295
|
+
- list: {} — the same as list_styles.
|
|
2296
|
+
- 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.
|
|
2297
|
+
- 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.
|
|
2298
|
+
- 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.
|
|
2253
2299
|
|
|
2254
2300
|
OVER PHOTOGRAPHS, PASS A PALETTE — NEVER THE DEFAULT. Measured on a real
|
|
2255
2301
|
render of captions over photographs: highlighter, sticker, caption and badge
|
|
@@ -2271,7 +2317,7 @@ EXAMPLE: { action: 'apply_style', itemId: 'title_1', styleKey: 'arcade', fontFam
|
|
|
2271
2317
|
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." },
|
|
2272
2318
|
text: { type: 'string', description: 'cursive: the words to write. to_collage: the text to build from, if it differs from the item.' },
|
|
2273
2319
|
maxWidth: { type: 'number', description: 'wrap: the width to break at, in canvas units. unwrap restores the original, so this is not destructive.' },
|
|
2274
|
-
cursiveOptions: { type: 'object', description:
|
|
2320
|
+
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." },
|
|
2275
2321
|
collageOptions: { type: 'object', description: 'to_collage: style, palette and the rest — same vocabulary as pinepaper_create_letter_collage.' },
|
|
2276
2322
|
itemId: { type: 'string', description: 'Text item id — apply_style / set_font_axes.' },
|
|
2277
2323
|
styleKey: { type: 'string', description: 'apply_style: style name from list_styles.' },
|
|
@@ -2309,13 +2355,15 @@ READ THIS BEFORE PROMISING A MEDIUM. Naming a medium does not create its marks.
|
|
|
2309
2355
|
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.
|
|
2310
2356
|
|
|
2311
2357
|
ACTIONS:
|
|
2358
|
+
- 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 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.
|
|
2312
2359
|
- 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).
|
|
2313
2360
|
- resolve: { medium } → { ok, fidelity, reason, markMaker }. ok:false with a reason is a real answer — pass it on rather than trying anyway.
|
|
2314
|
-
- list_stitches: the
|
|
2361
|
+
- list_stitches: the thread stitches with their params.
|
|
2315
2362
|
- apply_thread: render an item in thread. Its own silhouette is the region and its own fill is the thread colour, so an existing drawing becomes stitched rather than needing to be redrawn. The source is HIDDEN, not destroyed.
|
|
2316
2363
|
- apply_hatch: rule an item with hatching. Same deal — silhouette is the region, source is hidden, re-hatching replaces.
|
|
2317
2364
|
- list_flow_fields: the 7 fields that bend a hatch line.
|
|
2318
2365
|
- list_hatch_options: the hatch defaults.
|
|
2366
|
+
- 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.
|
|
2319
2367
|
|
|
2320
2368
|
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.
|
|
2321
2369
|
|
|
@@ -2341,11 +2389,17 @@ EXAMPLE — a stitched leaf:
|
|
|
2341
2389
|
inputSchema: {
|
|
2342
2390
|
type: 'object',
|
|
2343
2391
|
properties: {
|
|
2344
|
-
action: { type: 'string', enum: ['list_media', 'resolve', 'list_stitches', 'apply_thread', 'apply_hatch', 'list_flow_fields', 'list_hatch_options'], description: 'Medium operation' },
|
|
2345
|
-
medium: { type: 'string', description:
|
|
2346
|
-
|
|
2392
|
+
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' },
|
|
2393
|
+
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.` },
|
|
2394
|
+
options: { type: 'object', description: 'apply: the medium\'s own options (see the description; color takes hex).' },
|
|
2395
|
+
points: { type: 'array', items: {}, description: 'sdf_stroke: [{x, y, pressure?}] or [[x, y, pressure?]] in canvas px' },
|
|
2396
|
+
softness: { type: 'number', description: 'sdf_stroke: edge softness 0..1 (default 0.2)' },
|
|
2397
|
+
opacity: { type: 'number', description: 'sdf_stroke: 0..1 (default 1)' },
|
|
2398
|
+
resolution: { type: 'number', description: 'sdf_stroke: bitmap px per canvas px (default 2)' },
|
|
2399
|
+
name: { type: 'string', description: 'sdf_stroke: item name' },
|
|
2400
|
+
itemId: { type: 'string', description: 'apply / apply_thread / apply_hatch: a closed path, compound path (holes kept), or a group of them (applied per path).' },
|
|
2347
2401
|
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." },
|
|
2348
|
-
field: { type: 'object', description: "apply_thread: { kind: 'radial'|'spine'|'constant', cx, cy, angle, spine[], across } — default radial from the shape centre." },
|
|
2402
|
+
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." },
|
|
2349
2403
|
stitchLen: { type: 'number', description: 'apply_thread: nominal stitch length px (default 18).' },
|
|
2350
2404
|
rowGap: { type: 'number', description: 'apply_thread: row spacing px; defaults from thread width so rows abut.' },
|
|
2351
2405
|
variance: { type: 'number', description: 'apply_thread: length jitter 0..1 (default 0.35) — the long-and-short shading.' },
|
|
@@ -2384,12 +2438,12 @@ EXAMPLE — a stitched leaf:
|
|
|
2384
2438
|
idempotentHint: false,
|
|
2385
2439
|
openWorldHint: false,
|
|
2386
2440
|
},
|
|
2387
|
-
description: `Animate text CHARACTER BY CHARACTER —
|
|
2441
|
+
description: `Animate text CHARACTER BY CHARACTER — the terminaltexteffects vocabulary, reimplemented from source).
|
|
2388
2442
|
|
|
2389
2443
|
⚠️ 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.
|
|
2390
2444
|
|
|
2391
2445
|
ACTIONS:
|
|
2392
|
-
- list: {} —
|
|
2446
|
+
- list: {} — every effect as {key, label, definition}. Call this first; effect keys come from here.
|
|
2393
2447
|
- apply: { itemId, effect, duration?, seed?, gradient?, gradientStops?, gradientDirection?, gradientSteps?, keepSource?, options? } — explode and animate.
|
|
2394
2448
|
|
|
2395
2449
|
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.
|
|
@@ -2538,9 +2592,10 @@ RECIPE — assemble-from-pieces: shatter with keepSource, keyframe each tile fro
|
|
|
2538
2592
|
|
|
2539
2593
|
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.
|
|
2540
2594
|
|
|
2541
|
-
Layer tags
|
|
2595
|
+
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.
|
|
2542
2596
|
|
|
2543
|
-
Params: { info — the decomposer manifest, whose parts live under info.parts (or info.tag2pinfo) as an OBJECT KEYED BY TAG, not a layers[] array: {
|
|
2597
|
+
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? }
|
|
2598
|
+
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.
|
|
2544
2599
|
Returns: { groupId, parts, roles, rolesWired, warnings }
|
|
2545
2600
|
|
|
2546
2601
|
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.`,
|
|
@@ -2594,6 +2649,13 @@ RECIPE — a playable maze: create_tilemap with wall fills → batch_create colo
|
|
|
2594
2649
|
},
|
|
2595
2650
|
{
|
|
2596
2651
|
name: 'pinepaper_design_system',
|
|
2652
|
+
annotations: {
|
|
2653
|
+
title: 'Design System',
|
|
2654
|
+
readOnlyHint: false,
|
|
2655
|
+
destructiveHint: false,
|
|
2656
|
+
idempotentHint: false,
|
|
2657
|
+
openWorldHint: false,
|
|
2658
|
+
},
|
|
2597
2659
|
description: `The design vocabulary, as data you can read — and eighteen styles that can build a scene.
|
|
2598
2660
|
|
|
2599
2661
|
TWO DIFFERENT THINGS, kept apart on purpose:
|
|
@@ -2619,7 +2681,7 @@ A partial composition reports as a failure with the elements that did not create
|
|
|
2619
2681
|
title: { type: 'string', description: 'compose: the headline most styles build around.' },
|
|
2620
2682
|
subtitle: { type: 'string', description: 'compose: the supporting line.' },
|
|
2621
2683
|
body: { type: 'string', description: 'compose: body copy, where the style has room for it.' },
|
|
2622
|
-
width: { type: 'number', description: 'compose: canvas width
|
|
2684
|
+
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.' },
|
|
2623
2685
|
height: { type: 'number', description: 'compose: canvas height.' },
|
|
2624
2686
|
variant: { type: 'string', description: "compose: a style-specific variant, e.g. art deco's 'emerald'." },
|
|
2625
2687
|
draw: { type: 'boolean', description: 'compose: draw it (default true). false returns the scene and ops as data and draws nothing.' },
|
|
@@ -2630,13 +2692,20 @@ A partial composition reports as a failure with the elements that did not create
|
|
|
2630
2692
|
},
|
|
2631
2693
|
{
|
|
2632
2694
|
name: 'pinepaper_stick',
|
|
2695
|
+
annotations: {
|
|
2696
|
+
title: 'Stick Figure Kit',
|
|
2697
|
+
readOnlyHint: false,
|
|
2698
|
+
destructiveHint: false,
|
|
2699
|
+
idempotentHint: false,
|
|
2700
|
+
openWorldHint: false,
|
|
2701
|
+
},
|
|
2633
2702
|
description: `The vendored stick-figure kit — a rigged figure posed, walking, travelling, holding a prop, with a garment and hair, plus the set it stands on and among.
|
|
2634
2703
|
|
|
2635
2704
|
A different construction from pinepaper_character, which places a figure from the DESIGN GRAPH by concept ("pp:Pigeon"). This is the stick kit specifically.
|
|
2636
2705
|
|
|
2637
2706
|
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.
|
|
2638
2707
|
|
|
2639
|
-
action 'figure' builds the figure. action 'set' builds the floor, the wall and the objects around it, so a figure has somewhere to be.
|
|
2708
|
+
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.
|
|
2640
2709
|
|
|
2641
2710
|
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.`,
|
|
2642
2711
|
inputSchema: {
|
|
@@ -2644,7 +2713,7 @@ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact:
|
|
|
2644
2713
|
properties: {
|
|
2645
2714
|
action: { type: 'string', enum: ['figure', 'set'], description: "'figure' or 'set'." },
|
|
2646
2715
|
id: { type: 'string', description: 'Prefix for the created items.' },
|
|
2647
|
-
kind: { type: 'string', description: 'figure: which figure. set:
|
|
2716
|
+
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).' },
|
|
2648
2717
|
at: { type: 'object', description: '{ x?, y? } — where it goes.' },
|
|
2649
2718
|
scale: { type: 'number', description: 'Size multiplier.' },
|
|
2650
2719
|
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." },
|
|
@@ -2695,6 +2764,13 @@ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact:
|
|
|
2695
2764
|
},
|
|
2696
2765
|
{
|
|
2697
2766
|
name: 'pinepaper_story',
|
|
2767
|
+
annotations: {
|
|
2768
|
+
title: 'Story to Scene',
|
|
2769
|
+
readOnlyHint: false,
|
|
2770
|
+
destructiveHint: false,
|
|
2771
|
+
idempotentHint: false,
|
|
2772
|
+
openWorldHint: false,
|
|
2773
|
+
},
|
|
2698
2774
|
description: `A piece of prose becomes a scene.
|
|
2699
2775
|
|
|
2700
2776
|
- 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.
|
|
@@ -2715,17 +2791,36 @@ The geometry is vendored from mcp-cloud, which makes this a three-repo artifact:
|
|
|
2715
2791
|
},
|
|
2716
2792
|
{
|
|
2717
2793
|
name: 'pinepaper_interchange',
|
|
2794
|
+
annotations: {
|
|
2795
|
+
title: 'Interchange Formats',
|
|
2796
|
+
readOnlyHint: false,
|
|
2797
|
+
destructiveHint: false,
|
|
2798
|
+
idempotentHint: false,
|
|
2799
|
+
openWorldHint: false,
|
|
2800
|
+
},
|
|
2718
2801
|
description: `The formats other tools read and write — Lottie, dotLottie, GLB, BVH, PNG sequence.
|
|
2719
2802
|
|
|
2720
2803
|
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.
|
|
2721
2804
|
|
|
2722
|
-
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
|
|
2805
|
+
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.
|
|
2806
|
+
|
|
2807
|
+
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.
|
|
2808
|
+
|
|
2809
|
+
EXPORT:
|
|
2810
|
+
- export_lottie: { options?: { fps?, duration? (s), includeRelations? } } → a .json Lottie file
|
|
2811
|
+
- export_dotlottie: { options?: { fps?, duration? (s), id?, loop?, autoplay? } } → a .lottie file (the zipped Lottie plus manifest)
|
|
2812
|
+
- 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.
|
|
2813
|
+
- export_bvh: { skeletonId (required), options?: { fps? } } → a .bvh file of that skeleton's motion. Refused when nothing is rigged.
|
|
2814
|
+
- 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.
|
|
2815
|
+
|
|
2816
|
+
IMPORT:
|
|
2817
|
+
- 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.`,
|
|
2723
2818
|
inputSchema: {
|
|
2724
2819
|
type: 'object',
|
|
2725
2820
|
properties: {
|
|
2726
2821
|
action: { type: 'string', enum: ['export_lottie', 'export_dotlottie', 'import_lottie', 'export_glb', 'export_bvh', 'export_png_sequence'], description: 'Which interchange operation.' },
|
|
2727
2822
|
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." },
|
|
2728
|
-
data: { anyOf: [{ type: 'string' }, { type: 'object' }], description: 'import_lottie: the Lottie JSON — object
|
|
2823
|
+
data: { anyOf: [{ type: 'string' }, { type: 'object' }], description: 'import_lottie: the Lottie JSON — object or JSON string (a URL or path is not fetched).' },
|
|
2729
2824
|
skeletonId: { type: 'string', description: "export_bvh: which skeleton to write. A BVH is one skeleton's motion." },
|
|
2730
2825
|
},
|
|
2731
2826
|
required: ['action'],
|
|
@@ -2733,6 +2828,13 @@ Three of these refuse quietly in the engine: exportGLB with no perspective objec
|
|
|
2733
2828
|
},
|
|
2734
2829
|
{
|
|
2735
2830
|
name: 'pinepaper_sound',
|
|
2831
|
+
annotations: {
|
|
2832
|
+
title: 'Sound Synthesis & Soundtrack',
|
|
2833
|
+
readOnlyHint: false,
|
|
2834
|
+
destructiveHint: false,
|
|
2835
|
+
idempotentHint: false,
|
|
2836
|
+
openWorldHint: false,
|
|
2837
|
+
},
|
|
2736
2838
|
description: `Synthesis — and the drawing that is the same object.
|
|
2737
2839
|
|
|
2738
2840
|
The engine carries a full audio graph, and none of it was reachable. pinepaper_audio_beats ANALYSES audio that already exists; this MAKES audio.
|
|
@@ -2752,6 +2854,36 @@ THE CATALOGUE IS A STARTING SET, NOT THE LIMIT. Six instruments, five drums and
|
|
|
2752
2854
|
- For a drum, pass pitch: { from, tau }. The glide is what makes a kick a kick; without it you get a beep.
|
|
2753
2855
|
- Names are normalised to [a-z][a-z0-9_-]. Register 'Rhodes' and the response returns canonicalName 'rhodes' — play THAT.
|
|
2754
2856
|
|
|
2857
|
+
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) }.
|
|
2858
|
+
|
|
2859
|
+
CATALOGUES:
|
|
2860
|
+
- list_instruments / list_percussion / list_sfx: {} → the engine's names, built-in and defined.
|
|
2861
|
+
|
|
2862
|
+
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:
|
|
2863
|
+
- 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.
|
|
2864
|
+
- play_chord: { root ('C4'), chord? ('major' default; 'minor', 'maj7', 'dim' …), options? }
|
|
2865
|
+
- play_percussion / play_sfx: { name (from the catalogue), options? }
|
|
2866
|
+
- play_spec: { spec, options? } — options is merged into the spec.
|
|
2867
|
+
- play_from_text: { text, options? } — options is merged into the resolved spec.
|
|
2868
|
+
|
|
2869
|
+
READ WITHOUT PLAYING:
|
|
2870
|
+
- chord_frequencies: { root, chord?, options? } → { frequencies } (Hz)
|
|
2871
|
+
- from_text: { text ('a soft warm bell on A4') } → { spec } to inspect or edit, then play_spec or create
|
|
2872
|
+
- timbre_from_path: { itemId (a path with length), samples? (256) } → { timbre: { partials } }
|
|
2873
|
+
|
|
2874
|
+
ON THE CANVAS — a placed sound is a timeline item:
|
|
2875
|
+
- 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.
|
|
2876
|
+
- 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 }.
|
|
2877
|
+
- set_placement: { itemId, placement: { startTime?, duration? } } (seconds; no other keys are read) → { placed }
|
|
2878
|
+
- remove: { itemId } → { removed } · stop_all: {} → { stopped } — stops everything sounding now; placed sounds stay placed.
|
|
2879
|
+
|
|
2880
|
+
DEFINE (name is normalised to [a-z][a-z0-9_-]; play the returned canonicalName):
|
|
2881
|
+
- define_instrument: { name, partials (required, ≥1), envelope?, gain? (0.8), aliases? } → { canonicalName, requestedName, normalised, listed }
|
|
2882
|
+
- 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? }
|
|
2883
|
+
|
|
2884
|
+
RENDER:
|
|
2885
|
+
- 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.
|
|
2886
|
+
|
|
2755
2887
|
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.`,
|
|
2756
2888
|
inputSchema: {
|
|
2757
2889
|
type: 'object',
|
|
@@ -2786,10 +2918,17 @@ render_soundtrack mixes every placed sound to a WAV and writes it to a file. It
|
|
|
2786
2918
|
},
|
|
2787
2919
|
{
|
|
2788
2920
|
name: 'pinepaper_motion',
|
|
2921
|
+
annotations: {
|
|
2922
|
+
title: 'Group & Field Motion',
|
|
2923
|
+
readOnlyHint: false,
|
|
2924
|
+
destructiveHint: false,
|
|
2925
|
+
idempotentHint: false,
|
|
2926
|
+
openWorldHint: false,
|
|
2927
|
+
},
|
|
2789
2928
|
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.
|
|
2790
2929
|
|
|
2791
2930
|
Two kinds, and the second is the one nothing else here can do:
|
|
2792
|
-
- GROUP motions (
|
|
2931
|
+
- GROUP motions (driftX, driftY, swayX, swayY, rotate, pulse, wave, bounce) move the target as one.
|
|
2793
2932
|
- 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.
|
|
2794
2933
|
|
|
2795
2934
|
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.
|
|
@@ -2817,6 +2956,13 @@ This is not pinepaper_animate: that applies a loop preset to ONE item. This move
|
|
|
2817
2956
|
},
|
|
2818
2957
|
{
|
|
2819
2958
|
name: 'pinepaper_path',
|
|
2959
|
+
annotations: {
|
|
2960
|
+
title: 'Path Operations',
|
|
2961
|
+
readOnlyHint: false,
|
|
2962
|
+
destructiveHint: true,
|
|
2963
|
+
idempotentHint: false,
|
|
2964
|
+
openWorldHint: false,
|
|
2965
|
+
},
|
|
2820
2966
|
description: `Destructive path operations — the ones that REPLACE or ADD items rather than restyling them.
|
|
2821
2967
|
|
|
2822
2968
|
- boolean: unite | subtract | intersect | exclude | divide. CONSUMES its operands and leaves one result, so the input ids stop existing. The first id is the base; the rest apply to it in order.
|
|
@@ -2863,9 +3009,49 @@ ACTIONS:
|
|
|
2863
3009
|
- 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.
|
|
2864
3010
|
- remove_actor / list_actors / set_actor_pose: { actorId, pose: { x?, z?, angle? } } — the setter a timeline, sequencer or agent drives frame by frame.
|
|
2865
3011
|
- set_camera: { camera: { mode: 'follow' | 'fixed' | 'orbit', target? (actorId), radius?, speed?, eye?, lookAt? } }
|
|
2866
|
-
- add_object: { object: { x, z, height?, color
|
|
3012
|
+
- add_object: { object: { x, z, height?, color? } } (y defaults to sitting ON the terrain) / remove_object: { objectId }
|
|
2867
3013
|
- remove_world: {} — tears the world down; the Paper canvas was always its own layer and is untouched.
|
|
2868
3014
|
|
|
3015
|
+
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.
|
|
3016
|
+
|
|
3017
|
+
MESHES — a canvas path becomes real geometry:
|
|
3018
|
+
- 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.
|
|
3019
|
+
- lathe_path: { pathId, mesh?: same fields, plus arc? (DEGREES, 360 = full solid); segments? = steps round the revolution } → { meshId }
|
|
3020
|
+
- list_meshes: {} → { count, meshes } (long vertex arrays summarised to their length)
|
|
3021
|
+
- remove_mesh: { meshId }
|
|
3022
|
+
- set_mesh_instances: { meshId, instances: [[x, y, z, scale, rotY (RADIANS), variant], …] } — replaces the copies a mesh draws, six numbers each.
|
|
3023
|
+
|
|
3024
|
+
LIGHTS — point lights, at most 8 (the ninth is refused by name):
|
|
3025
|
+
- 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 }
|
|
3026
|
+
- set_light: { lightId, light: any of the add_light fields } — moves or retunes in place.
|
|
3027
|
+
- remove_light: { lightId } · list_lights: {} → { lights }
|
|
3028
|
+
|
|
3029
|
+
MATERIALS — a NAMED, SHARED surface: one edit restyles every object using it.
|
|
3030
|
+
- 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.
|
|
3031
|
+
- set_material: { materialId, material: any of the add_material fields } · remove_material: { materialId } · list_materials: {} → { materials }
|
|
3032
|
+
|
|
3033
|
+
IMPORT:
|
|
3034
|
+
- 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 }
|
|
3035
|
+
- 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.
|
|
3036
|
+
- list_mesh_clips: { meshId } → { clips } (the animation clips a skinned import carries)
|
|
3037
|
+
- set_mesh_clip: { meshId, clip, crossfade? (seconds) }
|
|
3038
|
+
|
|
3039
|
+
NAVIGATION, PICKING, CAMERA:
|
|
3040
|
+
- set_nav_target: { navTarget: '2d' | '3d' } — which layer the pointer drives · get_nav_target: {} → { navTarget }
|
|
3041
|
+
- ground_height: { point: { x, z } (WORLD) } → { height } of the terrain there
|
|
3042
|
+
- raycast: { origin: [x, y, z], direction: [x, y, z] (need not be normalised) } → { hit (registry id, point …) | null, missed }
|
|
3043
|
+
- line_of_sight: { from: [x, y, z], to: [x, y, z] } → { clear }
|
|
3044
|
+
- world_to_canvas: { point: { x, y, z? } (WORLD) } → { point: { x, y } } in canvas px; refused when the point is behind the camera
|
|
3045
|
+
- canvas_to_ground: { point: { x, y } (CANVAS px) } → { ground } where that pixel lands on the terrain
|
|
3046
|
+
- dolly_camera: { multiplier } (>1 away, <1 closer) → { camera }
|
|
3047
|
+
- pan_camera: { dx, dy } (SCREEN px) → { camera }
|
|
3048
|
+
|
|
3049
|
+
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).
|
|
3050
|
+
- 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 }
|
|
3051
|
+
- 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.
|
|
3052
|
+
- impulse: { bodyId, vector: [x, y, z] } · set_velocity: { bodyId, vector: [x, y, z] }
|
|
3053
|
+
- step_physics: { dt (seconds) } → { contacts, removed } · remove_body: { bodyId } · list_bodies: {} → { bodies }
|
|
3054
|
+
|
|
2869
3055
|
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).`,
|
|
2870
3056
|
inputSchema: {
|
|
2871
3057
|
type: 'object',
|
|
@@ -2879,7 +3065,7 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2879
3065
|
anyOf: [{ type: 'boolean' }, { type: 'object' }],
|
|
2880
3066
|
description: 'create: include the walkable character (boolean or config).',
|
|
2881
3067
|
},
|
|
2882
|
-
patch: { type: 'object', description: 'configure: partial world spec, validated against describe.' },
|
|
3068
|
+
patch: { type: 'object', description: 'configure: partial world spec, validated against describe. Colours (env.zenith, env.horizon, env.fogColor, env.sunColor, env.ambient, env.lowColor … env.propColorB) take hex ("#03050c") or [r, g, b] in 0..1.' },
|
|
2883
3069
|
actorId: { type: 'string', description: 'Actor id.' },
|
|
2884
3070
|
x: { type: 'number', description: 'add_actor: world x.' },
|
|
2885
3071
|
z: { type: 'number', description: 'add_actor: world z.' },
|
|
@@ -2890,18 +3076,18 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2890
3076
|
camera: { type: 'object', description: "set_camera: { mode: follow|fixed|orbit, target?, radius?, speed?, eye?, lookAt? }." },
|
|
2891
3077
|
object: { type: 'object', description: 'add_object: { x, z, height?, color?, y? }. metalness/roughness are accepted and IGNORED here — a plain object is drawn by the prop shader, which declares no such uniform. They render on the mesh path only.' },
|
|
2892
3078
|
pathId: { type: 'string', description: 'extrude_path / lathe_path: the canvas path to turn into geometry. The path itself is unchanged.' },
|
|
2893
|
-
mesh: { type: 'object', description: 'extrude_path / lathe_path options: { id?, depth
|
|
3079
|
+
mesh: { type: 'object', description: 'extrude_path / lathe_path options: { id?, depth? (WORLD units, default 1), unitsPerPixel? (canvas px → world units, default 0.01), upright? (extrude: stand the drawing up, extruded along Z; default flat, a floor plan extruded along Y), placement? (extrude: \'centered\' default: centred on x/z, base at y; \'canvas\': the outline\'s canvas position), caps?, segments?, arc?, flatness?, x?, y?, z?, rotY?, rotYDegrees?, scale?, doubleSided?, color?, metalness?, roughness? }. color is hex or [r,g,b] (default light grey — without it the mesh rendered black); it becomes the shader\'s diffuse uniform. arc is DEGREES; rotY is RADIANS (fed straight to cos/sin in the shader) — pass rotYDegrees to write degrees. These names are the ones the engine records as the mesh provenance, so a captured meshProvenance.opts spreads straight back in.' },
|
|
2894
3080
|
meshId: { type: 'string', description: 'remove_mesh: the mesh id.' },
|
|
2895
|
-
light: { type: 'object', description: 'add_light / set_light: { id?, x?, y?, z?, color
|
|
3081
|
+
light: { type: 'object', description: 'add_light / set_light: { id?, x?, y?, z?, color? (hex or linear [r, g, b], may exceed 1), intensity?, range? }. At most 8 point lights; the ninth is refused by name because the shader array is fixed-size.' },
|
|
2896
3082
|
lightId: { type: 'string', description: 'set_light / remove_light: the light id.' },
|
|
2897
|
-
material: { type: 'object', description: 'add_material / set_material: { 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.' },
|
|
3083
|
+
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.' },
|
|
2898
3084
|
materialId: { type: 'string', description: 'set_material / remove_material: the material id.' },
|
|
2899
|
-
source: { type: 'string', description: 'import_obj: the OBJ file TEXT. import_gltf:
|
|
3085
|
+
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).' },
|
|
2900
3086
|
importOptions: { type: 'object', description: 'import_obj / import_gltf: placement and scale options, passed through.' },
|
|
2901
3087
|
clip: { type: 'string', description: 'set_mesh_clip: which imported animation clip a skinned mesh plays.' },
|
|
2902
3088
|
crossfade: { type: 'number', description: 'set_mesh_clip: seconds to blend out of the previous clip (blended in LOCAL pose space).' },
|
|
2903
3089
|
navTarget: { type: 'string', enum: ['2d', '3d'], description: "set_nav_target: which layer the pointer drives." },
|
|
2904
|
-
point: { type: 'object', description: 'ground_height
|
|
3090
|
+
point: { type: 'object', description: 'ground_height: { x, z } in WORLD coordinates (y is read as z if z is absent). canvas_to_ground: { x, y } in canvas pixels. world_to_canvas: { x, y, z? } in world coordinates.' },
|
|
2905
3091
|
origin: { type: 'array', items: { type: 'number' }, description: 'raycast: ray origin [x, y, z].' },
|
|
2906
3092
|
direction: { type: 'array', items: { type: 'number' }, description: 'raycast: ray direction [x, y, z], need not be normalised.' },
|
|
2907
3093
|
multiplier: { type: 'number', description: 'dolly_camera: >1 away, <1 closer.' },
|
|
@@ -2924,7 +3110,9 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2924
3110
|
annotations: {
|
|
2925
3111
|
title: 'Character (place and direct a figure from the graph)',
|
|
2926
3112
|
readOnlyHint: false,
|
|
3113
|
+
destructiveHint: false,
|
|
2927
3114
|
idempotentHint: false,
|
|
3115
|
+
openWorldHint: false,
|
|
2928
3116
|
},
|
|
2929
3117
|
description: `Place a CHARACTER from the design graph and DIRECT it — no geometry, no bones, no poses.
|
|
2930
3118
|
|
|
@@ -2935,7 +3123,7 @@ The knowledge of how a figure is built and how it moves lives in the graph; this
|
|
|
2935
3123
|
{ at: 1.8, channel: "say", until: 4.2 },
|
|
2936
3124
|
{ at: 5.0, channel: "headTurn", value: -0.8 } ] }
|
|
2937
3125
|
|
|
2938
|
-
CHANNELS ARE DECLARED BY THE CONCEPT, not by you. Ask
|
|
3126
|
+
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:
|
|
2939
3127
|
- 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.
|
|
2940
3128
|
- 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.
|
|
2941
3129
|
- shift: every part moved together, for a turn a flat drawing cannot rotate into.
|
|
@@ -3023,7 +3211,7 @@ POSE MOTION — build a performance out of saved poses:
|
|
|
3023
3211
|
· plan: true returns the seams without touching the skeleton, so you can check the joins first.
|
|
3024
3212
|
- 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.
|
|
3025
3213
|
- 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.
|
|
3026
|
-
- bake_animation: freeze the whole rig into plain item keyframes for export. Needs items ATTACHED to bones
|
|
3214
|
+
- 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.
|
|
3027
3215
|
- add_secondary_motion: spring chains for tails, hair, cloth. skin_path: per-vertex deformation of a curved path.
|
|
3028
3216
|
|
|
3029
3217
|
ACTIONS:
|
|
@@ -3038,6 +3226,12 @@ ACTIONS:
|
|
|
3038
3226
|
- 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.
|
|
3039
3227
|
- 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.
|
|
3040
3228
|
- import_spine: { spineJson, rootPosition?, name? } → { skeletonId, bones, placeholders, animations, warnings } — import a Spine editor JSON export (bones, poses, attachment placeholders).
|
|
3229
|
+
- 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.
|
|
3230
|
+
- 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.
|
|
3231
|
+
- 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.
|
|
3232
|
+
- 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.
|
|
3233
|
+
- list_shape_keys: { skeletonId } → { shapeKeys: [{ id, name }], count }
|
|
3234
|
+
- load_shape_key: { skeletonId, shapeKeyId, weight? (0..1, default 1) } — blends the attached items toward a shape key saved with save_shape_key.
|
|
3041
3235
|
|
|
3042
3236
|
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.`,
|
|
3043
3237
|
inputSchema: {
|
|
@@ -3138,7 +3332,7 @@ EXAMPLE doc: { "nodes": [ {"id":"bar","type":"pp:Rectangle","width":300,"height"
|
|
|
3138
3332
|
width: { type: 'number' },
|
|
3139
3333
|
height: { type: 'number' },
|
|
3140
3334
|
},
|
|
3141
|
-
description: 'Canvas size
|
|
3335
|
+
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.',
|
|
3142
3336
|
},
|
|
3143
3337
|
defaultGeometry: {
|
|
3144
3338
|
type: 'object',
|
|
@@ -3316,7 +3510,7 @@ Relations are COMPOSITIONAL - an item can have multiple relations that work toge
|
|
|
3316
3510
|
relationType: {
|
|
3317
3511
|
type: 'string',
|
|
3318
3512
|
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'],
|
|
3319
|
-
description: 'Type of relationship',
|
|
3513
|
+
description: 'Type of relationship. A name registered with pinepaper_register_custom_relation in this session is accepted too.',
|
|
3320
3514
|
},
|
|
3321
3515
|
params: {
|
|
3322
3516
|
type: 'object',
|
|
@@ -3407,7 +3601,9 @@ USE WHEN:
|
|
|
3407
3601
|
idempotentHint: false,
|
|
3408
3602
|
openWorldHint: false,
|
|
3409
3603
|
},
|
|
3410
|
-
description: `
|
|
3604
|
+
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.
|
|
3605
|
+
|
|
3606
|
+
Register a custom relation type with compute and apply functions.
|
|
3411
3607
|
|
|
3412
3608
|
USE WHEN:
|
|
3413
3609
|
- Creating physics-based behaviors (attraction, repulsion, collision)
|
|
@@ -3472,7 +3668,7 @@ applyFunction: \`
|
|
|
3472
3668
|
annotations: {
|
|
3473
3669
|
title: 'Execute Custom Code',
|
|
3474
3670
|
readOnlyHint: false,
|
|
3475
|
-
destructiveHint:
|
|
3671
|
+
destructiveHint: true,
|
|
3476
3672
|
idempotentHint: false,
|
|
3477
3673
|
openWorldHint: true,
|
|
3478
3674
|
},
|
|
@@ -3646,12 +3842,9 @@ The authoritative list for a given item is pinepaper_get_animatable_properties
|
|
|
3646
3842
|
|
|
3647
3843
|
EASING — IT SHAPES THE SEGMENT ARRIVING AT THE KEYFRAME IT IS ON.
|
|
3648
3844
|
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.
|
|
3649
|
-
|
|
3650
|
-
-
|
|
3651
|
-
|
|
3652
|
-
- easeInOut: Slow start and end
|
|
3653
|
-
- bounce: Bounce effect
|
|
3654
|
-
- elastic: Elastic overshoot
|
|
3845
|
+
${EASING_LINES}
|
|
3846
|
+
- 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.
|
|
3847
|
+
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.
|
|
3655
3848
|
|
|
3656
3849
|
CLIP WINDOW (optional — Premiere/Canva-style clip behavior):
|
|
3657
3850
|
- timeOffset: Shift the whole clip in canvas time (seconds). The animation only runs while canvas time is in [timeOffset, timeOffset + (clipOutPoint - clipInPoint)].
|
|
@@ -3679,9 +3872,8 @@ EXAMPLE — Reveal at 2s, skip first second of keyframe data, end at 4s of keyfr
|
|
|
3679
3872
|
additionalProperties: true,
|
|
3680
3873
|
},
|
|
3681
3874
|
easing: {
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
description: 'Shapes the segment ARRIVING at this keyframe (from the previous one). Ignored on the first keyframe.',
|
|
3875
|
+
...KEYFRAME_EASING_JSON_SCHEMA,
|
|
3876
|
+
description: 'Shapes the segment ARRIVING at this keyframe (from the previous one). Ignored on the first keyframe. A name, or a custom cubic-bezier [x1, y1, x2, y2] — x in 0..1, y may exceed 0..1 for overshoot.',
|
|
3685
3877
|
},
|
|
3686
3878
|
},
|
|
3687
3879
|
required: ['time', 'properties'],
|
|
@@ -3732,6 +3924,8 @@ USE WHEN:
|
|
|
3732
3924
|
- Seeking to specific time
|
|
3733
3925
|
- Controlling animation state
|
|
3734
3926
|
|
|
3927
|
+
PLAYBACK: play { duration?, loop? } · pause {} (holds the playhead) · stop {} (stops and rewinds) · seek { time (s), deterministic? }
|
|
3928
|
+
|
|
3735
3929
|
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.
|
|
3736
3930
|
|
|
3737
3931
|
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.
|
|
@@ -3849,7 +4043,7 @@ MASK TYPES: rectangle, circle, ellipse, star, triangle, hexagon, heart, rounded`
|
|
|
3849
4043
|
properties: {
|
|
3850
4044
|
time: { type: 'number', description: 'Normalized time (0-1)' },
|
|
3851
4045
|
properties: { type: 'object', description: 'Mask properties (x, y, width, height, radius, scale, rotation, opacity)' },
|
|
3852
|
-
easing: { type: 'string', enum: [
|
|
4046
|
+
easing: { type: 'string', enum: [...KEYFRAME_EASINGS] },
|
|
3853
4047
|
},
|
|
3854
4048
|
required: ['time', 'properties'],
|
|
3855
4049
|
},
|
|
@@ -3860,7 +4054,7 @@ MASK TYPES: rectangle, circle, ellipse, star, triangle, hexagon, heart, rounded`
|
|
|
3860
4054
|
properties: {
|
|
3861
4055
|
startTime: { type: 'number', description: 'Start time in seconds', default: 0 },
|
|
3862
4056
|
duration: { type: 'number', description: 'Duration in seconds', default: 0.8 },
|
|
3863
|
-
easing: { type: 'string', enum: [
|
|
4057
|
+
easing: { type: 'string', enum: [...KEYFRAME_EASINGS], default: 'easeOut' },
|
|
3864
4058
|
reversed: { type: 'boolean', description: 'Reverse animation (hide instead of reveal)', default: false },
|
|
3865
4059
|
loop: { type: 'boolean', description: 'Loop the animation', default: false },
|
|
3866
4060
|
},
|
|
@@ -3977,7 +4171,7 @@ Needs a studio that publishes the item-side table; without one, the tool says so
|
|
|
3977
4171
|
idempotentHint: true,
|
|
3978
4172
|
openWorldHint: false,
|
|
3979
4173
|
},
|
|
3980
|
-
description: `[Utility]
|
|
4174
|
+
description: `[Utility] List the easing names the studio accepts: one table serves keyframes, masks, relations and the camera. Reads the live engine's list when it reports one.`,
|
|
3981
4175
|
inputSchema: {
|
|
3982
4176
|
type: 'object',
|
|
3983
4177
|
properties: {},
|
|
@@ -4101,8 +4295,8 @@ EXAMPLE (curved arc between waypoints):
|
|
|
4101
4295
|
pitch: { type: 'number', description: '3D tilt in degrees — rendered where the studio supports camera tilt (result.tiltRenders).' },
|
|
4102
4296
|
yaw: { type: 'number', description: '3D rotation in degrees — rendered where the studio supports camera tilt (result.tiltRenders).' },
|
|
4103
4297
|
easing: {
|
|
4104
|
-
|
|
4105
|
-
|
|
4298
|
+
...KEYFRAME_EASING_JSON_SCHEMA,
|
|
4299
|
+
description: 'A name, or a custom cubic-bezier [x1, y1, x2, y2] — x in 0..1, y may exceed 0..1 for overshoot.',
|
|
4106
4300
|
},
|
|
4107
4301
|
pathOut: {
|
|
4108
4302
|
type: 'array',
|
|
@@ -4958,7 +5152,7 @@ flags as a badly-composed scene, and they do not survive an artboard change.`,
|
|
|
4958
5152
|
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." },
|
|
4959
5153
|
stitch: { type: 'string', enum: [...THREAD_STITCHES], description: 'Which stitch when medium is thread (default longAndShort) — the same six pinepaper_design_medium offers.' },
|
|
4960
5154
|
stitchBudget: { type: 'number', description: 'Total marks across the composition (default 6000); the stitch scales to fit rather than the fill being cut short.' },
|
|
4961
|
-
action: { type: 'string', enum: ['list_patterns', 'list_treatments', 'apply', 'set_treatment'] },
|
|
5155
|
+
action: { type: 'string', enum: ['list_patterns', 'list_treatments', 'list_reveals', 'list_styles', 'apply', 'set_treatment'] },
|
|
4962
5156
|
pattern: { type: 'string', description: 'Pattern key from list_patterns' },
|
|
4963
5157
|
itemIds: { type: 'array', items: { type: 'string' }, description: 'Items in slot order' },
|
|
4964
5158
|
treatment: { type: 'string', description: 'Camera treatment key' },
|
|
@@ -4966,6 +5160,15 @@ flags as a badly-composed scene, and they do not survive an artboard change.`,
|
|
|
4966
5160
|
applyCamera: { type: 'boolean', description: 'false to arrange without compiling a camera track' },
|
|
4967
5161
|
loop: { type: 'boolean' },
|
|
4968
5162
|
craft: { type: 'object', description: 'Override craft ratios (gutter, margin…), as fractions of the short canvas edge' },
|
|
5163
|
+
// These were in the Zod schema and the description but not here, so a
|
|
5164
|
+
// client never saw them (1.6.19 gate C3).
|
|
5165
|
+
reveal: { type: ['string', 'null'], description: 'apply: temporal reveal key from list_reveals — items arriving over time' },
|
|
5166
|
+
revealOptions: { type: 'object', description: 'apply: reveal tuning { duration, beat, fade, beats: number[] }; explicit beats sync cuts to audio' },
|
|
5167
|
+
style: { type: 'string', description: 'apply: visual style bundle from list_styles (treatments + spacing + motion defaults)' },
|
|
5168
|
+
text: { type: 'array', items: { type: 'string' }, description: "apply: content for the pattern's TEXT slots (headline, caption…), in slot order" },
|
|
5169
|
+
assets: { type: 'array', items: { type: 'string' }, description: "apply: SVG markup for the pattern's VECTOR slots (badge, logo mark), in slot order" },
|
|
5170
|
+
audio: { type: 'string', description: 'apply: audio source to beat-sync the reveal to' },
|
|
5171
|
+
grid: { type: 'boolean', description: 'apply with audio: snap to an even tempo grid instead of raw onsets' },
|
|
4969
5172
|
},
|
|
4970
5173
|
required: ['action'],
|
|
4971
5174
|
},
|
|
@@ -4974,7 +5177,7 @@ flags as a badly-composed scene, and they do not survive an artboard change.`,
|
|
|
4974
5177
|
name: 'pinepaper_brand_kit',
|
|
4975
5178
|
annotations: {
|
|
4976
5179
|
title: 'Brand Kit', readOnlyHint: false, destructiveHint: false,
|
|
4977
|
-
idempotentHint: true, openWorldHint:
|
|
5180
|
+
idempotentHint: true, openWorldHint: true,
|
|
4978
5181
|
},
|
|
4979
5182
|
description: `Apply a brand kit — colours, fonts and roles — across the scene.
|
|
4980
5183
|
|
|
@@ -4982,6 +5185,7 @@ ACTIONS:
|
|
|
4982
5185
|
- plan: Report what WOULD change (count, per-role breakdown, WCAG contrast audit) WITHOUT touching
|
|
4983
5186
|
the canvas. Call this first on an unfamiliar scene; a whole-scene recolour is hard to eyeball afterwards.
|
|
4984
5187
|
- apply: Commit it. A history snapshot is taken FIRST, so one undo puts everything back.
|
|
5188
|
+
- 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.
|
|
4985
5189
|
|
|
4986
5190
|
KIT SHAPE — only name and colors.primary are required:
|
|
4987
5191
|
{ "name": "Acme", "colors": { "primary": "#e11d48", "secondary": "#0ea5e9",
|
|
@@ -4996,7 +5200,9 @@ Both actions return a WCAG contrast audit, so you can see failing pairs before s
|
|
|
4996
5200
|
inputSchema: {
|
|
4997
5201
|
type: 'object',
|
|
4998
5202
|
properties: {
|
|
4999
|
-
action: { type: 'string', enum: ['plan', 'apply'], description: 'plan (dry run) or
|
|
5203
|
+
action: { type: 'string', enum: ['plan', 'apply', 'from_url'], description: 'plan (dry run), apply, or from_url (read a site into a proposed kit)' },
|
|
5204
|
+
url: { type: 'string', description: "from_url: the product's site, http(s)." },
|
|
5205
|
+
screenshots: { type: 'integer', minimum: 1, maximum: 6, description: 'from_url: screenshots down the page (default 3).' },
|
|
5000
5206
|
kit: {
|
|
5001
5207
|
type: 'object',
|
|
5002
5208
|
description: 'Brand kit. Requires name and colors.primary; other roles are inferred.',
|
|
@@ -5022,7 +5228,7 @@ Both actions return a WCAG contrast audit, so you can see failing pairs before s
|
|
|
5022
5228
|
},
|
|
5023
5229
|
selectionOnly: { type: 'boolean', description: 'Restrict to current selection' },
|
|
5024
5230
|
},
|
|
5025
|
-
required: ['action'
|
|
5231
|
+
required: ['action'],
|
|
5026
5232
|
},
|
|
5027
5233
|
},
|
|
5028
5234
|
{
|
|
@@ -5149,13 +5355,13 @@ ACTIONS:
|
|
|
5149
5355
|
- lineage: Walk back to the roots — the chain of imports/derivations that produced it. Params: itemId
|
|
5150
5356
|
- dependents: What breaks if you delete it. Params: itemId — check this BEFORE removing something
|
|
5151
5357
|
other items were built from.
|
|
5152
|
-
- record: Note
|
|
5358
|
+
- record: Note where this item came from. Params: itemId, kind (${LINEAGE_KINDS.join(' | ')}; default derivedFrom), sourceRef (an item id, or asset:… / component:…), meta`,
|
|
5153
5359
|
inputSchema: {
|
|
5154
5360
|
type: 'object',
|
|
5155
5361
|
properties: {
|
|
5156
5362
|
action: { type: 'string', enum: ['get', 'lineage', 'dependents', 'record'] },
|
|
5157
5363
|
itemId: { type: 'string' },
|
|
5158
|
-
kind: { type: 'string',
|
|
5364
|
+
kind: { type: 'string', enum: [...LINEAGE_KINDS], description: 'record: lineage kind (default derivedFrom)' },
|
|
5159
5365
|
sourceRef: { type: 'string', description: 'What it came from (item id, asset id, URL)' },
|
|
5160
5366
|
meta: { type: 'object' },
|
|
5161
5367
|
},
|
|
@@ -5541,6 +5747,8 @@ ACTIONS:
|
|
|
5541
5747
|
- choose: Mood- and context-weighted recommendation based on subject/prompt words and target feeling.
|
|
5542
5748
|
- coverage: Report total indexable and describable capabilities across categories.
|
|
5543
5749
|
- find: Direct lookup of a capability by key to inspect its description and how to apply it (applyWith).
|
|
5750
|
+
- 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.
|
|
5751
|
+
- studio: {} — which engine methods THIS studio has, so you learn what will refuse before calling it.
|
|
5544
5752
|
|
|
5545
5753
|
MOOD VOCABULARY FOR CHOOSE:
|
|
5546
5754
|
- reveal: resolve, reveal, appear, emerge, decode, decrypt, unscramble, form, assemble
|
|
@@ -5738,7 +5946,8 @@ ACTIONS:
|
|
|
5738
5946
|
BEHAVIOR TYPES: repel, attract, follow, orbit, slingshot, physics_body, draggable_constrained
|
|
5739
5947
|
- 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).
|
|
5740
5948
|
|
|
5741
|
-
ACTION TYPES: navigate, show, hide, animate, stopAnimation, setState, incrementScore, playTimeline, pauseTimeline, seekTimeline, showFeedback, complete
|
|
5949
|
+
ACTION TYPES: navigate, show, hide, animate, stopAnimation, setState, incrementScore, playTimeline, pauseTimeline, seekTimeline, showFeedback, complete
|
|
5950
|
+
ACTION PARAMS: show / hide / stopAnimation: { items: [ids] } (itemId is accepted); animate: { items, animation?, speed? }; incrementScore: { points } (default 1; amount is accepted); setState: { key, value }.`,
|
|
5742
5951
|
inputSchema: {
|
|
5743
5952
|
type: 'object',
|
|
5744
5953
|
properties: {
|
|
@@ -5853,7 +6062,7 @@ TEMPLATE CATEGORIES (13):
|
|
|
5853
6062
|
EXAMPLES:
|
|
5854
6063
|
- List all templates: { "listOnly": true }
|
|
5855
6064
|
- List by category: { "category": "social-media", "listOnly": true }
|
|
5856
|
-
- Load template: { "templateId": "solar-system" }`,
|
|
6065
|
+
- Load template: { "templateId": "solar-system-education" }`,
|
|
5857
6066
|
inputSchema: {
|
|
5858
6067
|
type: 'object',
|
|
5859
6068
|
properties: {
|
|
@@ -6034,7 +6243,7 @@ RETURNS:
|
|
|
6034
6243
|
properties: {
|
|
6035
6244
|
id: {
|
|
6036
6245
|
type: 'string',
|
|
6037
|
-
description: '
|
|
6246
|
+
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.',
|
|
6038
6247
|
},
|
|
6039
6248
|
sourceItemId: {
|
|
6040
6249
|
type: 'string',
|
|
@@ -6131,7 +6340,7 @@ Same as pinepaper_connect: routing, lineColor, lineWidth, lineStyle, headStyle,
|
|
|
6131
6340
|
properties: {
|
|
6132
6341
|
id: {
|
|
6133
6342
|
type: 'string',
|
|
6134
|
-
description: '
|
|
6343
|
+
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.',
|
|
6135
6344
|
},
|
|
6136
6345
|
sourceItemId: {
|
|
6137
6346
|
type: 'string',
|
|
@@ -6572,14 +6781,13 @@ EXAMPLES:
|
|
|
6572
6781
|
idempotentHint: false,
|
|
6573
6782
|
openWorldHint: false,
|
|
6574
6783
|
},
|
|
6575
|
-
description: `Load a map and
|
|
6784
|
+
description: `Load a map and import/export its config. Call action: "load" first.
|
|
6576
6785
|
|
|
6577
6786
|
ACTIONS:
|
|
6578
|
-
- load — { mapId: "usa"|"world"|"worldHighRes"|<custom>, projection?, options? }
|
|
6579
|
-
- pan
|
|
6580
|
-
- zoom — { level, duration? }
|
|
6787
|
+
- 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
|
|
6788
|
+
- 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"
|
|
6581
6789
|
- export_config — {}
|
|
6582
|
-
- import_custom — { url? | geojson?,
|
|
6790
|
+
- import_custom — { url? | geoJson? (geojson accepted), options?: { projection?, fillColor? } }
|
|
6583
6791
|
|
|
6584
6792
|
For region styling/selection use pinepaper_map_regions; for animations use pinepaper_map_animation; for CSV/GeoJSON/source-info use pinepaper_map_data.`,
|
|
6585
6793
|
inputSchema: {
|
|
@@ -6588,6 +6796,7 @@ For region styling/selection use pinepaper_map_regions; for animations use pinep
|
|
|
6588
6796
|
action: { type: 'string', enum: ['load', 'pan', 'zoom', 'export_config', 'import_custom'], description: 'Map action' },
|
|
6589
6797
|
mapId: { type: 'string', description: 'Map identifier (load)' },
|
|
6590
6798
|
projection: { type: 'string', description: 'Projection (load / import_custom)' },
|
|
6799
|
+
center: { type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2, description: 'load: [lon, lat] the projection centres on' },
|
|
6591
6800
|
options: { type: 'object', description: 'Map options (load)', additionalProperties: true },
|
|
6592
6801
|
lat: { type: 'number', description: 'Latitude (pan)' },
|
|
6593
6802
|
lon: { type: 'number', description: 'Longitude (pan)' },
|
|
@@ -6611,15 +6820,17 @@ For region styling/selection use pinepaper_map_regions; for animations use pinep
|
|
|
6611
6820
|
description: `Highlight, color, label, and select map regions. Requires a map loaded via pinepaper_map { action: "load" } first.
|
|
6612
6821
|
|
|
6613
6822
|
ACTIONS:
|
|
6614
|
-
- highlight — { regionIds: string[], color?, opacity? }
|
|
6823
|
+
- highlight — { regionIds: string[], color?, opacity? } opacity 0..1 tints over the land: pre-blended over the region's land fill, drawn opaque
|
|
6615
6824
|
- unhighlight — { regionIds?: string[] } omit to clear all
|
|
6616
6825
|
- apply_colors — { data: Record<regionId, value>, options? } choropleth coloring
|
|
6617
6826
|
- add_marker — { lat, lon, label?, color?, size? }
|
|
6618
6827
|
- add_labels — { regions?: string[], style?, formatter? }
|
|
6619
|
-
- get_at_point — { x, y } region at canvas coords
|
|
6828
|
+
- get_at_point — { x, y } region id + name at canvas coords, and its (lon, lat)
|
|
6620
6829
|
- select — { regionIds: string[] }
|
|
6621
6830
|
- deselect — { regionIds?: string[] } omit to deselect all
|
|
6622
|
-
- get_highlighted — {}
|
|
6831
|
+
- get_highlighted — {} every region highlight OR apply_colors coloured (one colour channel)
|
|
6832
|
+
|
|
6833
|
+
highlight and apply_colors return the regions they coloured, by id, and name any id not on the map (notFound / unmatched).`,
|
|
6623
6834
|
inputSchema: {
|
|
6624
6835
|
type: 'object',
|
|
6625
6836
|
properties: {
|
|
@@ -6654,19 +6865,19 @@ ACTIONS:
|
|
|
6654
6865
|
description: `Animate map regions with keyframes or wave effects.
|
|
6655
6866
|
|
|
6656
6867
|
ACTIONS:
|
|
6657
|
-
- animate_regions — { regions:
|
|
6658
|
-
- animate_wave — {
|
|
6868
|
+
- animate_regions — { regions: { [regionId]: [{ time, fillColor (or color), opacity?, easing? }, …at least 2] }, duration?, loop? }
|
|
6869
|
+
- animate_wave — { colors: [at least 2], waveDirection? (horizontal | vertical | radial), waveSpeed?, duration?, loop? } — the wave runs across ALL regions
|
|
6659
6870
|
- stop — { regionIds?: string[] }
|
|
6660
6871
|
- get_animated — {}`,
|
|
6661
6872
|
inputSchema: {
|
|
6662
6873
|
type: 'object',
|
|
6663
6874
|
properties: {
|
|
6664
6875
|
action: { type: 'string', enum: ['animate_regions', 'animate_wave', 'stop', 'get_animated'], description: 'Animation action' },
|
|
6665
|
-
regions: {
|
|
6666
|
-
|
|
6876
|
+
regions: { description: 'animate_regions: an object of keyframes per region id. stop: an array of region ids to stop (omit for all).' },
|
|
6877
|
+
resetColors: { type: 'boolean', description: 'stop: reset regions to their default colours (default true).' },
|
|
6878
|
+
colors: { type: 'array', items: { type: 'string' }, description: 'animate_wave: colours the wave cycles through (at least 2).' },
|
|
6667
6879
|
waveDirection: { type: 'string', enum: ['horizontal', 'vertical', 'radial'] },
|
|
6668
|
-
|
|
6669
|
-
color: { type: 'string' },
|
|
6880
|
+
waveSpeed: { type: 'number' },
|
|
6670
6881
|
duration: { type: 'number' },
|
|
6671
6882
|
loop: { type: 'boolean' },
|
|
6672
6883
|
},
|
|
@@ -6685,8 +6896,8 @@ ACTIONS:
|
|
|
6685
6896
|
description: `CSV / GeoJSON import-export and source-info introspection.
|
|
6686
6897
|
|
|
6687
6898
|
ACTIONS:
|
|
6688
|
-
- export_csv — {
|
|
6689
|
-
- import_csv — { csv: string,
|
|
6899
|
+
- export_csv — { includeColors?, includeHighlighted?, includeSelected?, download?, filename? } — one row per region of the loaded map
|
|
6900
|
+
- import_csv — { csv (or csvText): string, applyColors?, applyHighlight?, applySelection? } — the CSV export_csv writes (one row per region id)
|
|
6690
6901
|
- export_geojson — { includeStyles?, includeMetadata?, selectedOnly?, download?, filename? }
|
|
6691
6902
|
- export_original_geojson — { download?, filename? } source GeoJSON before styling
|
|
6692
6903
|
- source_info — {} { source, projection, quality, regionCount, hasOriginalGeoJSON, isCustomImport }`,
|
|
@@ -6694,10 +6905,14 @@ ACTIONS:
|
|
|
6694
6905
|
type: 'object',
|
|
6695
6906
|
properties: {
|
|
6696
6907
|
action: { type: 'string', enum: ['export_csv', 'import_csv', 'export_geojson', 'export_original_geojson', 'source_info'], description: 'Data action' },
|
|
6697
|
-
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6908
|
+
csv: { type: 'string', description: 'import_csv: the CSV text (csvText is accepted too).' },
|
|
6909
|
+
csvText: { type: 'string' },
|
|
6910
|
+
applyColors: { type: 'boolean' },
|
|
6911
|
+
applyHighlight: { type: 'boolean' },
|
|
6912
|
+
applySelection: { type: 'boolean' },
|
|
6913
|
+
includeColors: { type: 'boolean' },
|
|
6914
|
+
includeHighlighted: { type: 'boolean' },
|
|
6915
|
+
includeSelected: { type: 'boolean' },
|
|
6701
6916
|
includeStyles: { type: 'boolean' },
|
|
6702
6917
|
includeMetadata: { type: 'boolean' },
|
|
6703
6918
|
selectedOnly: { type: 'boolean' },
|
|
@@ -7031,7 +7246,7 @@ EXAMPLES:
|
|
|
7031
7246
|
annotations: {
|
|
7032
7247
|
title: 'Execute Generator',
|
|
7033
7248
|
readOnlyHint: false,
|
|
7034
|
-
destructiveHint:
|
|
7249
|
+
destructiveHint: true,
|
|
7035
7250
|
idempotentHint: false,
|
|
7036
7251
|
openWorldHint: false,
|
|
7037
7252
|
},
|
|
@@ -7053,7 +7268,7 @@ USE WHEN:
|
|
|
7053
7268
|
- "grid background", "geometric abstract", "noise texture"
|
|
7054
7269
|
- Creating dynamic procedural backgrounds
|
|
7055
7270
|
|
|
7056
|
-
GENERATORS (
|
|
7271
|
+
GENERATORS (highlights; all ${GeneratorNameSchema.options.length} are in the generatorName enum):
|
|
7057
7272
|
|
|
7058
7273
|
Classic:
|
|
7059
7274
|
- drawSunburst: Radial rays from center (rayCount, colors, bgColor, opacity, rayGap, gradientRays)
|
|
@@ -7197,7 +7412,7 @@ glow's own default is 1.5. Pass a pixel count here and you will be wrong by
|
|
|
7197
7412
|
about two orders of magnitude. A recent studio warns about it
|
|
7198
7413
|
(kind: 'glow-size-unit'); an older one does not.
|
|
7199
7414
|
|
|
7200
|
-
EFFECTS (
|
|
7415
|
+
EFFECTS (${EffectTypeSchema.options.length}: particles, then the shader auras from heatmap on):
|
|
7201
7416
|
- sparkle: Glitter/sparkle particles (color, speed, size)
|
|
7202
7417
|
- blast: Explosion burst effect (color, radius, count)
|
|
7203
7418
|
- smoke: Rising smoke plumes (color, speed, size, drift, height, growthRate)
|
|
@@ -7267,27 +7482,17 @@ USE WHEN:
|
|
|
7267
7482
|
- Post-processing the entire scene
|
|
7268
7483
|
- User says "make it look vintage", "add blur", "black and white"
|
|
7269
7484
|
|
|
7270
|
-
AVAILABLE FILTERS:
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
- blur: Gaussian blur effect (params: radius 0-20)
|
|
7274
|
-
- brightness: Adjust brightness (params: value -100 to 100)
|
|
7275
|
-
- contrast: Adjust contrast (params: value -100 to 100)
|
|
7276
|
-
- saturation: Adjust color saturation (params: value -100 to 100). 'saturate' is accepted as the same filter.
|
|
7277
|
-
- invert: Invert colors (params: intensity 0-1)
|
|
7278
|
-
- noise: Add film grain (params: intensity 0-100, monochrome: true/false)
|
|
7279
|
-
- vignette: Darken edges (params: intensity 0-1, radius 0-1)
|
|
7280
|
-
- vintage: Preset combining sepia, vignette, noise
|
|
7281
|
-
- colorOverlay: Add color tint (params: color, intensity, blendMode)
|
|
7282
|
-
- sharpen: Increase sharpness (params: intensity 0-100)
|
|
7283
|
-
- emboss: 3D emboss (params: intensity 0-1)
|
|
7284
|
-
- posterize: Reduce color levels (params: levels 2-32)
|
|
7485
|
+
AVAILABLE FILTERS (the engine's own list, with each one's parameters):
|
|
7486
|
+
${FILTER_TYPES.map((f) => `- ${f}: ${FILTER_DOCS[f]}`).join('\n')}
|
|
7487
|
+
'saturate' is accepted as saturation.
|
|
7285
7488
|
|
|
7286
7489
|
EXAMPLES:
|
|
7287
7490
|
- "Make it black and white" → filterType: "grayscale"
|
|
7288
7491
|
- "Add vintage look" → filterType: "vintage", params: {intensity: 0.8}
|
|
7289
7492
|
- "Blur the background" → filterType: "blur", params: {radius: 5}
|
|
7290
7493
|
- "Increase contrast" → filterType: "contrast", params: {value: 30}
|
|
7494
|
+
- "Print it like a comic" → filterType: "halftoneCMYK", params: {size: 6}
|
|
7495
|
+
- "8-bit / retro screen" → filterType: "dither", params: {levels: 4}
|
|
7291
7496
|
|
|
7292
7497
|
Filters can be stacked - call multiple times to combine effects.`,
|
|
7293
7498
|
inputSchema: {
|
|
@@ -7295,7 +7500,7 @@ Filters can be stacked - call multiple times to combine effects.`,
|
|
|
7295
7500
|
properties: {
|
|
7296
7501
|
filterType: {
|
|
7297
7502
|
type: 'string',
|
|
7298
|
-
enum: [
|
|
7503
|
+
enum: [...FILTER_TYPES],
|
|
7299
7504
|
description: 'Type of filter to apply',
|
|
7300
7505
|
},
|
|
7301
7506
|
params: {
|
|
@@ -7448,28 +7653,204 @@ OUTPUT FORMAT:
|
|
|
7448
7653
|
idempotentHint: true,
|
|
7449
7654
|
openWorldHint: false,
|
|
7450
7655
|
},
|
|
7451
|
-
description: `Export the
|
|
7656
|
+
description: `Export the scene: a SUMMARY for inspection by default, or with full: true a RESTORABLE project document saved to a file.
|
|
7657
|
+
|
|
7658
|
+
SUMMARY (default) — for reading the scene, not for saving it:
|
|
7659
|
+
- items (id, type, position, bounds, active window), relations, decorative items, backgroundColor, canvasSize
|
|
7660
|
+
- It does NOT carry keyframe tracks, rigging or scene chains, so a scene rebuilt from it has no motion. The result says so (note).
|
|
7661
|
+
|
|
7662
|
+
FULL (full: true) — to save work and restore it later:
|
|
7663
|
+
- The engine's own project document (relations, keyframes, rigging, connectors, scene chains, animation config), written to a file because it can be megabytes.
|
|
7664
|
+
- Returns filePath, bytes and counts (items, animated items, keyframes, relations). Restore with pinepaper_import_scene { path } and compare the counts.
|
|
7452
7665
|
|
|
7453
7666
|
USE WHEN:
|
|
7454
|
-
-
|
|
7455
|
-
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7667
|
+
- full: true — saving a scene to come back to, a backup, a hand-off to another session
|
|
7668
|
+
- summary — debugging composition, checking what is on the canvas`,
|
|
7669
|
+
inputSchema: {
|
|
7670
|
+
type: 'object',
|
|
7671
|
+
properties: {
|
|
7672
|
+
full: { type: 'boolean', description: 'Save a restorable project document to a file instead of returning the summary.' },
|
|
7673
|
+
name: { type: 'string', description: 'full: a name recorded in the document.' },
|
|
7674
|
+
},
|
|
7675
|
+
},
|
|
7676
|
+
},
|
|
7677
|
+
{
|
|
7678
|
+
name: 'pinepaper_styled_scene',
|
|
7679
|
+
annotations: {
|
|
7680
|
+
title: 'Styled Scene',
|
|
7681
|
+
readOnlyHint: false,
|
|
7682
|
+
destructiveHint: false,
|
|
7683
|
+
idempotentHint: true,
|
|
7684
|
+
openWorldHint: false,
|
|
7685
|
+
},
|
|
7686
|
+
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.
|
|
7458
7687
|
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7688
|
+
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.
|
|
7689
|
+
|
|
7690
|
+
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}.
|
|
7691
|
+
|
|
7692
|
+
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.
|
|
7693
|
+
|
|
7694
|
+
KNOW:
|
|
7695
|
+
- Calling again with the same id REPLACES the scene, which is how to change style.
|
|
7696
|
+
- dither and ascii need a Chrome renderer (WebKit ignores the canvas filter they use).
|
|
7697
|
+
- The scene redraws every frame through a render hook. After a page reload it is a still image until this is called again.
|
|
7698
|
+
- It sits below every other item, sized to cover the canvas, so titles and overlays go on top as usual.
|
|
7699
|
+
|
|
7700
|
+
EXAMPLE: { style: 'ink', duration: 6 }`,
|
|
7465
7701
|
inputSchema: {
|
|
7466
7702
|
type: 'object',
|
|
7467
|
-
properties: {
|
|
7703
|
+
properties: {
|
|
7704
|
+
style: { type: 'string', description: `${STYLED_SCENE_STYLES.join(' | ')} (default watercolor)` },
|
|
7705
|
+
duration: { type: 'number', description: 'Seconds the scene runs (default 8). Export for this long.' },
|
|
7706
|
+
id: { type: 'string', description: 'Scene id (default "styled-scene"); the same id replaces the scene.' },
|
|
7707
|
+
spec: { type: 'object', description: "The composition; omitted keys take the 'valley' preset.", additionalProperties: true },
|
|
7708
|
+
},
|
|
7709
|
+
},
|
|
7710
|
+
},
|
|
7711
|
+
{
|
|
7712
|
+
name: 'pinepaper_relight',
|
|
7713
|
+
annotations: {
|
|
7714
|
+
title: 'Relight the 3D World',
|
|
7715
|
+
readOnlyHint: false,
|
|
7716
|
+
destructiveHint: false,
|
|
7717
|
+
idempotentHint: true,
|
|
7718
|
+
openWorldHint: false,
|
|
7719
|
+
},
|
|
7720
|
+
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.
|
|
7721
|
+
|
|
7722
|
+
ACTIONS:
|
|
7723
|
+
- set: { lights, ambient?, strength?, enabled?, preview? } — install or replace the rig. Up to 8 lights:
|
|
7724
|
+
point: { type: 'point', x, y, z? (120), radius? (400), color?, intensity? (1), specular? (32) }
|
|
7725
|
+
directional: { type: 'directional', dir: [dx, dy, dz], color?, intensity? } [0, 0, -1] faces the viewer
|
|
7726
|
+
Coordinates are export-frame px; colours are '#rrggbb' or [r, g, b]. ambient default 0.2, strength (normal-map) default 2.
|
|
7727
|
+
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.
|
|
7728
|
+
- get: the current rig.
|
|
7729
|
+
- clear: remove it.
|
|
7730
|
+
- list_lights: the light types.
|
|
7731
|
+
|
|
7732
|
+
LIMITS — say them before you promise a look:
|
|
7733
|
+
- 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).
|
|
7734
|
+
- Normals come from the world's brightness, not real depth, so it reads as raking light over the picture rather than true 3D shading.
|
|
7735
|
+
- It lights the 3D world layer only, not 2D items drawn over it.
|
|
7736
|
+
- The rig is NOT saved with the project: set it again after reloading.
|
|
7737
|
+
- A studio without the relight pass refuses with the reason.
|
|
7738
|
+
|
|
7739
|
+
EXAMPLE: { action: 'set', lights: [{ type: 'point', x: 1500, y: 200, z: 300, radius: 1200, color: '#ffd9a0', intensity: 1.4 }], ambient: 0.15 }`,
|
|
7740
|
+
inputSchema: {
|
|
7741
|
+
type: 'object',
|
|
7742
|
+
properties: {
|
|
7743
|
+
action: { type: 'string', enum: ['set', 'get', 'clear', 'list_lights'], description: 'Relight operation' },
|
|
7744
|
+
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?}" },
|
|
7745
|
+
ambient: { type: 'number', description: 'set: ambient light (default 0.2)' },
|
|
7746
|
+
strength: { type: 'number', description: 'set: normal-map strength (default 2)' },
|
|
7747
|
+
enabled: { type: 'boolean', description: 'set: false keeps the rig but turns it off' },
|
|
7748
|
+
preview: { description: 'set: true / false, or {maxSide} for the live preview overlay', oneOf: [{ type: 'boolean' }, { type: 'object', properties: { maxSide: { type: 'number' } } }] },
|
|
7749
|
+
},
|
|
7750
|
+
required: ['action'],
|
|
7751
|
+
},
|
|
7752
|
+
},
|
|
7753
|
+
{
|
|
7754
|
+
name: 'pinepaper_shader_graph',
|
|
7755
|
+
annotations: {
|
|
7756
|
+
title: 'Shader Graph',
|
|
7757
|
+
readOnlyHint: false,
|
|
7758
|
+
destructiveHint: false,
|
|
7759
|
+
idempotentHint: false,
|
|
7760
|
+
openWorldHint: false,
|
|
7761
|
+
},
|
|
7762
|
+
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.
|
|
7763
|
+
|
|
7764
|
+
ACTIONS:
|
|
7765
|
+
- node_types: the node vocabulary with each node's params. Call this first.
|
|
7766
|
+
- validate: { graph } — check it, store nothing. Errors are named (unknown type, duplicate id, dangling edge, cycle, branching).
|
|
7767
|
+
- create: { graph } → graphId — validate and store.
|
|
7768
|
+
- get / remove: { graphId }. list: the stored graphs.
|
|
7769
|
+
- apply: { target, graph | graphId, keepSource?, asItem? }
|
|
7770
|
+
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.
|
|
7771
|
+
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.
|
|
7772
|
+
|
|
7773
|
+
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.
|
|
7774
|
+
|
|
7775
|
+
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.
|
|
7776
|
+
|
|
7777
|
+
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.
|
|
7778
|
+
|
|
7779
|
+
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' }] } }`,
|
|
7780
|
+
inputSchema: {
|
|
7781
|
+
type: 'object',
|
|
7782
|
+
properties: {
|
|
7783
|
+
action: { type: 'string', enum: ['node_types', 'validate', 'create', 'get', 'list', 'remove', 'apply'], description: 'Shader graph operation' },
|
|
7784
|
+
graph: { type: 'object', description: 'validate / create / apply: {id?, nodes: [{id, type, params?}], edges: [{from, to}]}', additionalProperties: true },
|
|
7785
|
+
graphId: { type: 'string', description: 'get / remove / apply: a stored graph id' },
|
|
7786
|
+
target: { type: 'string', description: "apply: an itemId, or 'scene'" },
|
|
7787
|
+
keepSource: { type: 'boolean', description: 'apply to an item: keep the source visible' },
|
|
7788
|
+
asItem: { type: 'boolean', description: "apply to 'scene': add the result as an image item" },
|
|
7789
|
+
},
|
|
7790
|
+
required: ['action'],
|
|
7791
|
+
},
|
|
7792
|
+
},
|
|
7793
|
+
{
|
|
7794
|
+
name: 'pinepaper_place_on_surface',
|
|
7795
|
+
annotations: {
|
|
7796
|
+
title: 'Place on Surface',
|
|
7797
|
+
readOnlyHint: false,
|
|
7798
|
+
destructiveHint: false,
|
|
7799
|
+
idempotentHint: true,
|
|
7800
|
+
openWorldHint: false,
|
|
7801
|
+
},
|
|
7802
|
+
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.
|
|
7803
|
+
|
|
7804
|
+
- 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.
|
|
7805
|
+
- 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.
|
|
7806
|
+
- 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.
|
|
7807
|
+
- Tuning: occlusion ('luma' | 'none'), key, erode, matteBlur, soften, glare, spill, spillRadius, tint [r, g, b]. hideSource (default true) hides the UI item once placed.`,
|
|
7808
|
+
inputSchema: {
|
|
7809
|
+
type: 'object',
|
|
7810
|
+
properties: {
|
|
7811
|
+
photoId: { type: 'string', description: 'The photo (an image item).' },
|
|
7812
|
+
sourceId: { type: 'string', description: 'The UI to place (any item or group, or an image).' },
|
|
7813
|
+
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.' },
|
|
7814
|
+
quadSpace: { type: 'string', enum: ['image', 'canvas'], description: "quad's coordinates (default 'image', the photo's pixels)." },
|
|
7815
|
+
occlusion: { type: 'string', enum: ['luma', 'none'], description: "'luma' (default) keeps a hand in front of the screen." },
|
|
7816
|
+
key: { type: 'number', description: 'Brightness that counts as screen (default: found from the photo).' },
|
|
7817
|
+
erode: { type: 'number', description: 'Pull the matte in from the bezel, px (default 2).' },
|
|
7818
|
+
matteBlur: { type: 'number', description: 'Matte edge softness (default 0.8).' },
|
|
7819
|
+
soften: { type: 'number', description: 'Soften the UI to the photo (default 0.5).' },
|
|
7820
|
+
glare: { type: 'number', description: 'Glass glare 0..1 (default 0.10).' },
|
|
7821
|
+
spill: { type: 'number', description: 'Light spill 0..1 (default 0.35).' },
|
|
7822
|
+
spillRadius: { type: 'number', description: 'Spill reach, px (default 40).' },
|
|
7823
|
+
tint: { type: 'array', items: { type: 'number' }, minItems: 3, maxItems: 3, description: 'Multiply the UI by [r, g, b] 0..1 to match white balance.' },
|
|
7824
|
+
slot: { type: 'string', description: "Screen name (default 'screen'); the same slot replaces." },
|
|
7825
|
+
hideSource: { type: 'boolean', description: 'Hide the UI item after placing (default true).' },
|
|
7826
|
+
},
|
|
7827
|
+
required: ['photoId', 'sourceId'],
|
|
7828
|
+
},
|
|
7829
|
+
},
|
|
7830
|
+
{
|
|
7831
|
+
name: 'pinepaper_import_scene',
|
|
7832
|
+
annotations: {
|
|
7833
|
+
title: 'Import Scene',
|
|
7834
|
+
readOnlyHint: false,
|
|
7835
|
+
destructiveHint: true,
|
|
7836
|
+
idempotentHint: true,
|
|
7837
|
+
openWorldHint: false,
|
|
7838
|
+
},
|
|
7839
|
+
description: `Restore a scene from a project document saved by pinepaper_export_scene { full: true }. REPLACES the current scene.
|
|
7840
|
+
|
|
7841
|
+
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.
|
|
7842
|
+
|
|
7843
|
+
- path: the file the export wrote (absolute, or relative to the server's working directory).
|
|
7844
|
+
- strict: false loads a document with validation problems anyway and returns them as warnings (default true: refuse).`,
|
|
7845
|
+
inputSchema: {
|
|
7846
|
+
type: 'object',
|
|
7847
|
+
properties: {
|
|
7848
|
+
path: { type: 'string', description: 'The project document file.' },
|
|
7849
|
+
strict: { type: 'boolean', description: 'Refuse a document with validation problems (default true).' },
|
|
7850
|
+
},
|
|
7851
|
+
required: ['path'],
|
|
7468
7852
|
},
|
|
7469
7853
|
},
|
|
7470
|
-
// ---------------------------------------------------------------------------
|
|
7471
|
-
// INFRASTRUCTURE: BROWSER CONTROL TOOLS
|
|
7472
|
-
// ---------------------------------------------------------------------------
|
|
7473
7854
|
{
|
|
7474
7855
|
name: 'pinepaper_browser_connect',
|
|
7475
7856
|
annotations: {
|
|
@@ -7725,7 +8106,7 @@ PRIVACY: No screenshots, no tool call arguments, no user content — only struct
|
|
|
7725
8106
|
|
|
7726
8107
|
WORKFLOW: start_job → batch_execute (everything in ONE call) → end_job (screenshot) → show user → iterate if needed.
|
|
7727
8108
|
|
|
7728
|
-
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.
|
|
8109
|
+
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.
|
|
7729
8110
|
|
|
7730
8111
|
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)`,
|
|
7731
8112
|
inputSchema: {
|
|
@@ -7872,7 +8253,7 @@ EXAMPLES:
|
|
|
7872
8253
|
annotations: {
|
|
7873
8254
|
title: 'Build Scene — Items, Animations, Effects in One Call',
|
|
7874
8255
|
readOnlyHint: false,
|
|
7875
|
-
destructiveHint:
|
|
8256
|
+
destructiveHint: true,
|
|
7876
8257
|
idempotentHint: false,
|
|
7877
8258
|
openWorldHint: false,
|
|
7878
8259
|
},
|
|
@@ -7890,17 +8271,17 @@ WORKFLOW:
|
|
|
7890
8271
|
1. pinepaper_agent_start_job → 2. pinepaper_agent_batch_execute (everything) → 3. pinepaper_agent_end_job (screenshot for validation)
|
|
7891
8272
|
After validation: user reviews screenshot → feedback → modify/recreate as needed.
|
|
7892
8273
|
|
|
7893
|
-
OPERATION TYPES (
|
|
8274
|
+
OPERATION TYPES (${AgentBatchOperationTypeSchema.options.length}) — use in this order:
|
|
7894
8275
|
|
|
7895
8276
|
CANVAS SETUP:
|
|
7896
|
-
set_canvas_size — {width, height} or {preset: "instagram"|"youtube"|"tiktok"|...}
|
|
8277
|
+
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)
|
|
7897
8278
|
set_background — {backgroundColor: "#hex"}
|
|
7898
8279
|
execute_generator — {generatorName, generatorParams} → fills canvas with procedural art (paints WITHOUT fully registering items — see get_items; custom generator names need skipValidation:true)
|
|
7899
8280
|
Generators: drawBokeh, drawGradientMesh, drawWaves, drawSunburst, drawSunsetScene, drawGrid, drawCircuit, drawPattern, drawStackedCircles, drawGeometricAbstract, drawWindField, drawFluidFlow, drawOrganicFlow, drawNoiseTexture, drawGlobeWireframe
|
|
7900
8281
|
PineMath: drawFunctionPlot (y=f(x) plots), drawParametricCurve (x(t),y(t)), drawSimulation (pendulum, Lorenz, spring-mass), drawSpectrumAnalyzer (FFT), draw3DSurface (torus, sphere, Klein bottle)
|
|
7901
8282
|
|
|
7902
8283
|
ITEMS:
|
|
7903
|
-
create — {itemType, position: {x,y}, properties}
|
|
8284
|
+
create — {itemType, position: {x,y} or [x,y], properties}
|
|
7904
8285
|
itemType: text, circle, rectangle, star, triangle, polygon, ellipse, path, line, arc, pentagon, hexagon, diamond, arrow, heart
|
|
7905
8286
|
Properties per type:
|
|
7906
8287
|
text: {content, fontSize, fontFamily, color, fontWeight}
|
|
@@ -7942,14 +8323,14 @@ ANIMATION NOTES:
|
|
|
7942
8323
|
|
|
7943
8324
|
EXAMPLE — Animated sky scene with timed reveals:
|
|
7944
8325
|
{operations: [
|
|
7945
|
-
{type: "set_canvas_size", preset: "
|
|
8326
|
+
{type: "set_canvas_size", preset: "full-hd-1080p"},
|
|
7946
8327
|
{type: "execute_generator", generatorName: "drawGradientMesh", generatorParams: {colors: ["#1e3a5f", "#87CEEB", "#fbbf24"], bgColor: "#0c1445", blobCount: 4}},
|
|
7947
8328
|
{type: "create", itemType: "circle", position: {x: 1600, y: 150}, properties: {radius: 80, color: "#fbbf24", shadowColor: "#fbbf24", shadowBlur: 40}},
|
|
7948
8329
|
{type: "create", itemType: "ellipse", position: {x: 400, y: 180}, properties: {radiusX: 100, radiusY: 35, color: "#ffffff", opacity: 0.8}},
|
|
7949
8330
|
{type: "create", itemType: "ellipse", position: {x: 900, y: 120}, properties: {radiusX: 120, radiusY: 40, color: "#ffffff", opacity: 0.7}},
|
|
7950
8331
|
{type: "create", itemType: "text", position: {x: 960, y: 500}, properties: {content: "Dream Big", fontSize: 96, color: "#ffffff", fontWeight: "bold"}},
|
|
7951
8332
|
{type: "animate", itemId: "$0", animationType: "pulse", animationOptions: {speed: 0.3}},
|
|
7952
|
-
{type: "animate", itemId: "$1", animationType: "
|
|
8333
|
+
{type: "animate", itemId: "$1", animationType: "slideLeftRight", animationOptions: {speed: 0.2}},
|
|
7953
8334
|
{type: "animate", itemId: "$2", animationType: "slide", animationOptions: {speed: 0.15, direction: "left"}},
|
|
7954
8335
|
{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},
|
|
7955
8336
|
{type: "apply_mask", itemId: "$3", maskPreset: "wipeLeft"},
|
|
@@ -7986,9 +8367,11 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
7986
8367
|
description: 'For create: item type',
|
|
7987
8368
|
},
|
|
7988
8369
|
position: {
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
8370
|
+
oneOf: [
|
|
8371
|
+
{ type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } } },
|
|
8372
|
+
{ type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2 },
|
|
8373
|
+
],
|
|
8374
|
+
description: 'For create: position {x, y} or [x, y]',
|
|
7992
8375
|
},
|
|
7993
8376
|
properties: { type: 'object', description: 'For create/modify: item properties' },
|
|
7994
8377
|
itemId: { type: 'string', description: 'Target item ID or $N reference' },
|
|
@@ -7998,7 +8381,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
7998
8381
|
// Loop animation
|
|
7999
8382
|
animationType: {
|
|
8000
8383
|
type: 'string',
|
|
8001
|
-
enum: [
|
|
8384
|
+
enum: [...SimpleAnimationTypeSchema.options],
|
|
8002
8385
|
description: 'For animate: loop animation type',
|
|
8003
8386
|
},
|
|
8004
8387
|
animationOptions: { type: 'object', description: 'For animate: {speed, amplitude, direction}' },
|
|
@@ -8022,7 +8405,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
8022
8405
|
targetId: { type: 'string', description: 'For relation: target item ID or $N' },
|
|
8023
8406
|
relationType: {
|
|
8024
8407
|
type: 'string',
|
|
8025
|
-
enum: [
|
|
8408
|
+
enum: [...RelationTypeSchema.options],
|
|
8026
8409
|
description: 'For relation: type',
|
|
8027
8410
|
},
|
|
8028
8411
|
relationOptions: { type: 'object', description: 'For relation: options' },
|
|
@@ -8037,7 +8420,7 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
8037
8420
|
// Effects
|
|
8038
8421
|
effectType: {
|
|
8039
8422
|
type: 'string',
|
|
8040
|
-
enum: [
|
|
8423
|
+
enum: [...EffectTypeSchema.options],
|
|
8041
8424
|
description: 'For apply_effect: effect type',
|
|
8042
8425
|
},
|
|
8043
8426
|
effectParams: { type: 'object', description: 'For apply_effect: parameters' },
|
|
@@ -8112,7 +8495,7 @@ Held exports last until released, or until a LATER export needs the space — an
|
|
|
8112
8495
|
{
|
|
8113
8496
|
name: 'pinepaper_agent_export',
|
|
8114
8497
|
annotations: {
|
|
8115
|
-
title: 'Export
|
|
8498
|
+
title: 'Export Video, Stills, Animation, Ads, Captions or Audio',
|
|
8116
8499
|
readOnlyHint: true,
|
|
8117
8500
|
destructiveHint: false,
|
|
8118
8501
|
idempotentHint: true,
|
|
@@ -8141,7 +8524,7 @@ PLATFORMS & OPTIMAL FORMATS:
|
|
|
8141
8524
|
|
|
8142
8525
|
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.
|
|
8143
8526
|
|
|
8144
|
-
FORMATS:
|
|
8527
|
+
FORMATS: ${AgentExportFormatSchema.options.join(', ')}.
|
|
8145
8528
|
QUALITY: draft, standard, high.
|
|
8146
8529
|
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).
|
|
8147
8530
|
|
|
@@ -8164,7 +8547,7 @@ VERIFY MOTION BEFORE YOU RENDER. An export takes seconds to minutes and shows yo
|
|
|
8164
8547
|
platform: {
|
|
8165
8548
|
type: 'string',
|
|
8166
8549
|
enum: ['auto', 'instagram', 'instagram-story', 'tiktok', 'youtube', 'youtube-thumbnail', 'twitter', 'linkedin', 'web', 'print-a4', 'print-letter', 'print-a4-landscape', 'print-letter-landscape'],
|
|
8167
|
-
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
|
|
8550
|
+
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.",
|
|
8168
8551
|
},
|
|
8169
8552
|
format: {
|
|
8170
8553
|
type: 'string',
|
|
@@ -8195,6 +8578,14 @@ VERIFY MOTION BEFORE YOU RENDER. An export takes seconds to minutes and shows yo
|
|
|
8195
8578
|
broadcast: { type: 'boolean', description: 'mp4 only: broadcast-safe — BT.709, limited range (16-235), tagged bt709, constant bitrate with an 8 Mbps floor at 720p+ (4 below). result.video reports what the encoder did.' },
|
|
8196
8579
|
frames: { type: 'integer', minimum: 1, maximum: 36000, description: 'Animated formats: exactly this many frames instead of duration. A duration makes ceil(duration x fps) frames (7.91667 s at 24 fps is 191, not 190), so use frames for frame-exact segments.' },
|
|
8197
8580
|
deterministic: { type: 'boolean', description: 'mp4 / webm: byte-identical files for the same scene (pins the container timestamps), so exports can be checksummed or cached by hash.' },
|
|
8581
|
+
motionBlur: {
|
|
8582
|
+
type: 'object',
|
|
8583
|
+
description: 'mp4 / webm: real motion blur. Each frame is rendered `subframes` times across the shutter and averaged, so fast movement smears instead of strobing. Costs subframes× the render time: run estimateOnly first (it reports renders and renderCost). The result carries motionBlur (what the exporter applied).',
|
|
8584
|
+
properties: {
|
|
8585
|
+
subframes: { type: 'integer', minimum: 1, maximum: 32, description: 'Renders per frame (default 8; 1 = no blur).' },
|
|
8586
|
+
shutter: { type: 'number', minimum: 0, maximum: 1, description: 'Fraction of the frame interval the shutter is open (default 0.5).' },
|
|
8587
|
+
},
|
|
8588
|
+
},
|
|
8198
8589
|
broadcastHeadroom: { type: 'integer', minimum: 0, maximum: 40, description: 'mp4 with broadcast only, default 12: luma codes kept clear at both ends, so encoder ringing stays legal (12 measured clean on hard edges; 4 = ~100x fewer out-of-range samples, 12 = ~1000x; never zero). Fidelity raises luma_out_of_range with the legaliser command when any remain.' },
|
|
8199
8590
|
bitrate: { type: 'integer', minimum: 100000, maximum: 200000000, description: 'mp4 / webm: target bits/s, replacing the quality-derived one. The browser encoder treats it as a CEILING; result.video.achievedBitrate is what it produced.' },
|
|
8200
8591
|
minBitrate: { type: 'integer', minimum: 100000, maximum: 200000000, description: 'mp4 / webm: a floor; a miss is a fidelity warning naming the re-encode a delivery spec needs.' },
|
|
@@ -8572,6 +8963,14 @@ Three-phase analysis:
|
|
|
8572
8963
|
|
|
8573
8964
|
Quality tiers: basic (<0.4), fair (0.4-0.6), good (0.6-0.8), excellent (>=0.8)
|
|
8574
8965
|
|
|
8966
|
+
DEFINITION SHAPE (a template document; id, name, category and data are REQUIRED, the rest optional; items and relations sit under data):
|
|
8967
|
+
{ id, name, category, dimensions?: { width, height }, duration? (s), description?, tags?: [..], semantics?: {..},
|
|
8968
|
+
data: {
|
|
8969
|
+
items: [{ id, type ('circle', 'text' …), x?, y? | position?: [x, y], content?, animationType?, keyframes?: [{ time, properties: {..}, easing? }], mask?: { type } }],
|
|
8970
|
+
relations: [{ type ('orbits' …), source | from, target | to, params? }],
|
|
8971
|
+
backgroundGenerator?, backgroundGeneratorParams?
|
|
8972
|
+
} }
|
|
8973
|
+
|
|
8575
8974
|
USE WHEN:
|
|
8576
8975
|
- Checking a template for structural issues before publishing
|
|
8577
8976
|
- Comparing quality scores between template variants
|
|
@@ -8757,12 +9156,12 @@ USE WHEN:
|
|
|
8757
9156
|
- You want more/less detail in tool descriptions → switch verbosity
|
|
8758
9157
|
|
|
8759
9158
|
PROFILES:
|
|
8760
|
-
- full:
|
|
8761
|
-
- agent:
|
|
9159
|
+
- full: Every tool, no filtering
|
|
9160
|
+
- agent: Every tool except the diagnostics (get_performance_metrics, diagnostic_report) and register_item — default
|
|
8762
9161
|
- diagram: Diagram/flowchart tools
|
|
8763
9162
|
- map: Map/choropleth tools
|
|
8764
9163
|
- font: Font creation tools
|
|
8765
|
-
- minimal: Bare essentials
|
|
9164
|
+
- minimal: Bare essentials
|
|
8766
9165
|
|
|
8767
9166
|
VERBOSITY:
|
|
8768
9167
|
- verbose: Full descriptions
|
|
@@ -8988,7 +9387,7 @@ ACTIONS:
|
|
|
8988
9387
|
- set_rulers: Show/hide rulers. Params: enabled (bool)
|
|
8989
9388
|
- set_grid: Show/hide unit grid. Params: enabled (bool)
|
|
8990
9389
|
- get_dimensions: Get item bounds. Params: itemId → returns {x, y, width, height, rotation}
|
|
8991
|
-
- set_snap: Enable/disable
|
|
9390
|
+
- 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.`,
|
|
8992
9391
|
inputSchema: {
|
|
8993
9392
|
type: 'object',
|
|
8994
9393
|
properties: {
|