@pinepaper.studio/mcp-server 1.6.13 → 1.6.15
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 +6 -1
- package/README.es.md +6 -1
- package/README.fr.md +6 -1
- package/README.hi.md +6 -1
- package/README.ja.md +6 -1
- package/README.ko.md +6 -1
- package/README.md +245 -6
- package/README.pt-BR.md +6 -1
- package/README.zh-CN.md +6 -1
- package/assets/brand/pinepaper-lockup-horizontal-white.svg +1 -0
- package/assets/brand/pinepaper-lockup-horizontal.svg +1 -0
- package/assets/edit-whenever.svg +1 -1
- package/dist/browser/puppeteer-controller.js +2 -2
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/build-info.json +5 -0
- package/dist/design/design-systems.js +1 -1
- package/dist/design/design-systems.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/ontology/upstream-vocabulary.d.ts +23 -0
- package/dist/ontology/upstream-vocabulary.d.ts.map +1 -0
- package/dist/ontology/upstream-vocabulary.js +1172 -0
- package/dist/ontology/upstream-vocabulary.js.map +1 -0
- package/dist/ontology/vocabulary.d.ts.map +1 -1
- package/dist/ontology/vocabulary.js +19 -0
- package/dist/ontology/vocabulary.js.map +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +264 -70
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +24 -2
- 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 +37 -10
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/handlers/camera.d.ts.map +1 -1
- package/dist/tools/handlers/camera.js +17 -2
- package/dist/tools/handlers/camera.js.map +1 -1
- package/dist/tools/handlers/font.d.ts.map +1 -1
- package/dist/tools/handlers/font.js +48 -3
- package/dist/tools/handlers/font.js.map +1 -1
- package/dist/tools/handlers/maps.d.ts.map +1 -1
- package/dist/tools/handlers/maps.js +16 -0
- package/dist/tools/handlers/maps.js.map +1 -1
- package/dist/tools/handlers.d.ts +11 -1
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +426 -14
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +2 -0
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/shape-params.d.ts +13 -2
- package/dist/tools/shape-params.d.ts.map +1 -1
- package/dist/tools/shape-params.js +98 -3
- 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 +2 -0
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +27 -1
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +2506 -273
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +1483 -239
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +259 -26
- package/dist/types/schemas.js.map +1 -1
- package/dist/utils/a11y.d.ts +67 -0
- package/dist/utils/a11y.d.ts.map +1 -0
- package/dist/utils/a11y.js +117 -0
- package/dist/utils/a11y.js.map +1 -0
- package/dist/utils/ad-package.d.ts +44 -0
- package/dist/utils/ad-package.d.ts.map +1 -0
- package/dist/utils/ad-package.js +121 -0
- package/dist/utils/ad-package.js.map +1 -0
- package/dist/utils/scc.d.ts +41 -0
- package/dist/utils/scc.d.ts.map +1 -0
- package/dist/utils/scc.js +185 -0
- package/dist/utils/scc.js.map +1 -0
- package/dist/utils/zip.d.ts +7 -0
- package/dist/utils/zip.d.ts.map +1 -0
- package/dist/utils/zip.js +82 -0
- package/dist/utils/zip.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +31 -1
- package/dist/version.js.map +1 -1
- package/icon.png +0 -0
- package/icon.svg +1 -0
- package/manifest.json +11 -2
- package/package.json +8 -4
|
@@ -11,7 +11,7 @@ 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
13
|
import { GeneratorNameSchema, ItemTypeSchema } from '../types/schemas.js';
|
|
14
|
-
import { EffectTypeSchema, SimpleAnimationTypeSchema, GEOMETRY_OPERATIONS, THREAD_STITCHES, } from '../types/schemas.js';
|
|
14
|
+
import { EffectTypeSchema, SimpleAnimationTypeSchema, EasingSchema, GEOMETRY_OPERATIONS, THREAD_STITCHES, } from '../types/schemas.js';
|
|
15
15
|
/**
|
|
16
16
|
* Vocabulary lists derived from Zod schemas so tool descriptions stay in
|
|
17
17
|
* sync automatically. The description-schema-parity test
|
|
@@ -21,6 +21,10 @@ import { EffectTypeSchema, SimpleAnimationTypeSchema, GEOMETRY_OPERATIONS, THREA
|
|
|
21
21
|
*/
|
|
22
22
|
const EFFECT_TYPES_LIST = EffectTypeSchema.options.join(', ');
|
|
23
23
|
const SIMPLE_ANIMATION_TYPES_LIST = SimpleAnimationTypeSchema.options.join(', ');
|
|
24
|
+
// The easings a keyframe accepts. The batch description once listed
|
|
25
|
+
// easeInCubic / easeOutCubic / easeOutBounce, which keyframe_animate rejects
|
|
26
|
+
// (round 8 DD, 1.68) — derived now, so it cannot say what the schema refuses.
|
|
27
|
+
const KEYFRAME_EASINGS_LIST = EasingSchema.options.join(', ');
|
|
24
28
|
// =============================================================================
|
|
25
29
|
// AI AGENT GUIDE (exported for pinepaper_tool_guide)
|
|
26
30
|
// =============================================================================
|
|
@@ -257,12 +261,12 @@ COMMON PRESETS:
|
|
|
257
261
|
- hd-landscape: 1920x1080
|
|
258
262
|
- hd-portrait: 1080x1920
|
|
259
263
|
|
|
260
|
-
Max canvas size:
|
|
264
|
+
Max canvas size: 8192x8192 (a 5400x1350 carousel panorama fits). For wedding invitations, event cards, or detailed designs, use at least 1080x1080 or larger.`,
|
|
261
265
|
inputSchema: {
|
|
262
266
|
type: 'object',
|
|
263
267
|
properties: {
|
|
264
|
-
width: { type: 'number', description: 'Canvas width in pixels (100-
|
|
265
|
-
height: { type: 'number', description: 'Canvas height in pixels (100-
|
|
268
|
+
width: { type: 'number', description: 'Canvas width in pixels (100-8192)' },
|
|
269
|
+
height: { type: 'number', description: 'Canvas height in pixels (100-8192)' },
|
|
266
270
|
preset: {
|
|
267
271
|
type: 'string',
|
|
268
272
|
description: 'Optional preset name',
|
|
@@ -400,6 +404,10 @@ its own size. anchor: 'top-left' (also top-right, bottom-left, bottom-right,
|
|
|
400
404
|
center; origin is an alias for the parameter) fixes that without you computing
|
|
401
405
|
bounds you cannot know until the item exists. It is applied after size and
|
|
402
406
|
rotation, so it uses the item's real box.
|
|
407
|
+
PIXEL-EXACT SLOTS (a spec'd 615x345 end-screen box, a UI frame): use anchor
|
|
408
|
+
'top-left' with WHOLE-NUMBER x, y, width and height. A centred box with an odd
|
|
409
|
+
width or height has its edges on half pixels, which renders 1 px short with a
|
|
410
|
+
soft anti-aliased edge (measured: 615x345 centred came out 614x344).
|
|
403
411
|
|
|
404
412
|
EVERY type below also takes a label, which wraps the item in a labelled group —
|
|
405
413
|
you do not need a second create call and a hand-computed position to caption a
|
|
@@ -413,7 +421,12 @@ Labelling a text item gives you TWO text items in the group — the body and the
|
|
|
413
421
|
label. That is what you want for a caption; it is a surprise if you expected
|
|
414
422
|
the label to replace the content.
|
|
415
423
|
|
|
416
|
-
- text: Text content with font styling (properties: content, fontSize, color, fontFamily)
|
|
424
|
+
- text: Text content with font styling (properties: content, fontSize, color, fontFamily, justification, fontWeight, fontStyle, leading, lineHeight)
|
|
425
|
+
fontWeight: 400 / 700 / 'bold'. fontStyle: 'italic'. leading: line spacing in px; lineHeight: a multiple of fontSize (<= 4) or px. Multi-line content splits on \n.
|
|
426
|
+
fit: {maxWidth, maxHeight?, minFontSize? (8), maxFontSize? (current), wrap? (true), hold? ('top' | 'center' | …)} — auto-size the text into a box, keeping its anchored edge. The box is remembered: changing content later refits (shrinks, or grows back to maxFontSize); modify_item {fit: {...}} sets a new box; {fit: null} stops fitting. The result's textFit says {fontSize, lines, fits}; fits:false means it overflows even at minFontSize. Studios without auto-fit say so in textFit. Every export's fidelity then lists text_overflow for any text past its box or off the frame — the check to run before a batch of variants.
|
|
427
|
+
direction: 'auto' (default: the first strong letter decides, so Arabic and Hebrew need nothing) | 'ltr' | 'rtl' — the paragraph's base direction, which places neutrals like the final period (aliases textDirection, dir). The result's direction says whether the studio applied it; a studio without RTL support lays text out left-to-right and says so. justification: 'right' is alignment, not direction.
|
|
428
|
+
tabularFigures: true (or fontVariantNumeric: 'tabular-nums', or fontFeatures containing 'tnum') — fixed-width digits, so a counter, price or timer does not jitter as it changes. The result's tabularFigures says whether the studio applied them.
|
|
429
|
+
strokePosition: 'outside' — the stroke sits entirely outside the letters (drawn under the fill at twice strokeWidth), so a thick caption / meme outline keeps the letterform; 'center' (default) is the ordinary stroke. The result's strokePosition says whether the studio applied it.
|
|
417
430
|
- circle: Circular shape (properties: radius, color, strokeColor, strokeWidth)
|
|
418
431
|
- star: Star shape (properties: radius1, radius2, points, color)
|
|
419
432
|
radius1 is the OUTER radius and is what sizes the star; radius or
|
|
@@ -450,6 +463,7 @@ the label to replace the content.
|
|
|
450
463
|
positioned it. Offset the points yourself, or use sides + radius + position.
|
|
451
464
|
- ellipse: Oval shape (properties: width, height, color) — defaults to 100x60
|
|
452
465
|
- path: Custom path with segments or SVG data (properties: segments, pathData, strokeColor, fillColor, closed, smooth)
|
|
466
|
+
color on a path: a CLOSED path (closed:true, or pathData ending in Z) is FILLED with it; an OPEN path is STROKED with it. Pass fillColor / strokeColor to say exactly which.
|
|
453
467
|
- line: Line between two points (properties: from, to, strokeColor, strokeWidth)
|
|
454
468
|
- arc: Curved arc through three points (properties: from, through, to, strokeColor)
|
|
455
469
|
- pentagon: Regular 5-sided polygon (properties: radius, color)
|
|
@@ -496,6 +510,7 @@ Use itemType: "path" with either:
|
|
|
496
510
|
|
|
497
511
|
ADVANCED STYLING (NEW):
|
|
498
512
|
All shapes support these additional properties:
|
|
513
|
+
- No fill (stroke-only outlines, line art): fillColor: null, 'transparent' or 'none', with a strokeColor and strokeWidth.
|
|
499
514
|
- Gradients: color can be an object: {type: "linear"|"radial", stops: [{color: "#fff", offset: 0}, {color: "#000", offset: 1}], origin: [x,y], destination: [x,y]}
|
|
500
515
|
- Shadows: shadowColor, shadowBlur (pixels), shadowOffset: [x, y]
|
|
501
516
|
- Blend modes: blendMode: "multiply"|"screen"|"overlay"|"darken"|"lighten"|etc.
|
|
@@ -574,6 +589,11 @@ LIFETIME — on ANY item, and the only way to build a piece that CUTS between sh
|
|
|
574
589
|
- ttl: seconds it lives for. Omit for "until the end".
|
|
575
590
|
Without these every item is on screen for the whole render, which is what makes
|
|
576
591
|
a long piece read as one crowded frame instead of a sequence of shots.
|
|
592
|
+
They set the item's VISIBILITY for [bornAt, bornAt + ttl) — not its opacity —
|
|
593
|
+
so they compose with any keyframes, opacity fades included. Re-timing with
|
|
594
|
+
pinepaper_modify_item {bornAt, ttl} replaces the previous lifetime — pass both.
|
|
595
|
+
The result's lifetime field says whether it was applied; it is runtime-only
|
|
596
|
+
until the studio reads bornAt / ttl itself, so re-time a reopened scene.
|
|
577
597
|
|
|
578
598
|
SHADER (itemType 'shader') — a lit surface, drawn per pixel:
|
|
579
599
|
- shader: 'water' (open sea, lit swell) | 'liquid_metal' (chrome) | 'heatmap' (thermal halo) | 'gem_smoke' (smoke) | 'electric_arc' (branching discharge, stepped flicker) | 'vortex' (hollow bright RING, sheared by differential rotation) | 'rain_veil' | 'caustics' | 'ink_bleed'
|
|
@@ -596,6 +616,11 @@ thousands of authored items.
|
|
|
596
616
|
- Example — a rolling swell: xExpr "(i % 120) * 8", yExpr "400 + sin((i % 120) * 0.4 - t * 2) * 12", count 3600`,
|
|
597
617
|
additionalProperties: true,
|
|
598
618
|
},
|
|
619
|
+
anchor: {
|
|
620
|
+
type: 'string',
|
|
621
|
+
enum: ['center', 'top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
|
622
|
+
description: "Which point of the item `position` names (default center). Same as properties.anchor — for a pixel-exact slot use 'top-left' with whole numbers.",
|
|
623
|
+
},
|
|
599
624
|
data: {
|
|
600
625
|
type: 'object',
|
|
601
626
|
description: 'Item data flags for selection and behavior control',
|
|
@@ -668,6 +693,7 @@ MODIFIABLE PROPERTIES:
|
|
|
668
693
|
- content: Text content
|
|
669
694
|
- opacity: Transparency (0-1)
|
|
670
695
|
- rotation: Rotation in degrees
|
|
696
|
+
- skewX / skewY: shear in degrees about the item's centre; matrix: [a, b, c, d, tx, ty] affine. For mockups (a box face, a screen at an angle). APPLIED to the current shape, so calling again compounds — the result's affine says so.
|
|
671
697
|
- scale: Size multiplier
|
|
672
698
|
- shadowColor: Shadow color
|
|
673
699
|
- shadowBlur: Shadow blur radius
|
|
@@ -966,6 +992,8 @@ USE WHEN:
|
|
|
966
992
|
- Adding logos or vector graphics
|
|
967
993
|
- User mentions SVG files or vector graphics
|
|
968
994
|
|
|
995
|
+
CHECK LINE-ART IMPORTS: fill="none" and stroke-dasharray have been measured to be lost on import — outline icons come in filled, dashed strokes come in solid. For line art, check the result with a screenshot; to fix it, set fillColor: null (and dashArray) on the imported paths with pinepaper_modify_item.
|
|
996
|
+
|
|
969
997
|
⚠️ RECOMMENDED FOR COMPLEX CHARACTERS:
|
|
970
998
|
Instead of drawing "a witch" or "a cat" with many path operations, use SVG import:
|
|
971
999
|
1. Describe what you need and use a simple placeholder SVG
|
|
@@ -1129,6 +1157,11 @@ EXAMPLES:
|
|
|
1129
1157
|
enum: ['circle', 'rounded', 'hexagon', 'star'],
|
|
1130
1158
|
description: 'Optional mask shape to apply to the image',
|
|
1131
1159
|
},
|
|
1160
|
+
smoothing: {
|
|
1161
|
+
type: 'string',
|
|
1162
|
+
enum: ['off', 'low', 'medium', 'high'],
|
|
1163
|
+
description: "Resampling when the image is scaled. 'off' = nearest-neighbour, hard pixel edges — use it for pixel art. Default 'low'. Change it later with pinepaper_modify_item {smoothing}.",
|
|
1164
|
+
},
|
|
1132
1165
|
},
|
|
1133
1166
|
required: ['url'],
|
|
1134
1167
|
},
|
|
@@ -1539,7 +1572,7 @@ warp (optional): chained parametric warp(s) — displace each sampled point by {
|
|
|
1539
1572
|
|
|
1540
1573
|
RETURNS: { success, kind, itemId } — itemId is the created path. Bad expressions / empty ranges return { success:false, error }.
|
|
1541
1574
|
|
|
1542
|
-
To make an item TRAVEL along an equation curve (instead of drawing it), use pinepaper_add_relation relationType moves_along_path with params.equation instead.
|
|
1575
|
+
To make an item TRAVEL along an equation curve (instead of drawing it), use pinepaper_add_relation relationType moves_along_path with params.equation — but note that equation is read ONLY on the relation's signal path. An option that leaves that path takes the equation with it and the item simply does not move: easing 'bounce' is piecewise and falls back, so bounce + equation is a static item reporting success. Use a signal-friendly easing (linear, easeIn, easeOut, easeInOut, sine, pingpong) with an equation, or supply params.path instead.
|
|
1543
1576
|
|
|
1544
1577
|
EXAMPLES:
|
|
1545
1578
|
- Sine wave: { kind: "function", expr: "sin(x/40) * 60", min: -300, max: 300, style: { strokeColor: "#4f46e5" } }
|
|
@@ -1564,7 +1597,7 @@ EXAMPLES:
|
|
|
1564
1597
|
y: { type: 'number', description: 'Canvas origin y (default: canvas center)' },
|
|
1565
1598
|
flipY: { type: 'boolean', description: 'Math y-up → screen y-down (default true)' },
|
|
1566
1599
|
style: { type: 'object', description: 'Path style forwarded to create (strokeColor, strokeWidth, fillColor, …)' },
|
|
1567
|
-
solveOde: { type: 'object', description: "Integrate an ODE instead of plotting a closed form, and return the SOLUTION rather than drawing it: { equations, initialState, tEnd?, dt?, method? ('rk4' default, or 'euler' — faster and visibly wrong on anything stiff) }. The trajectory comes back as data, to inspect, feed to a path, or drive keyframes with." },
|
|
1600
|
+
solveOde: { type: 'object', description: "Integrate an ODE instead of plotting a closed form, and return the SOLUTION rather than drawing it: { equations, initialState, tEnd?, dt?, method? ('rk4' default, or 'euler' — faster and visibly wrong on anything stiff) }. STATE VARIABLES ARE POSITIONAL: each expression is written in terms of t, y0, y1, y2 ... where yN is the Nth entry of initialState. There is no x or y, so a Lorenz system is ['10*(y1-y0)', 'y0*(28-y2)-y1', 'y0*y1-(8/3)*y2'] with initialState [1,1,1] — writing it as '10*(y-x)' fails with \"Undefined symbol y\". The trajectory comes back as data, to inspect, feed to a path, or drive keyframes with." },
|
|
1568
1601
|
warp: {
|
|
1569
1602
|
anyOf: [{ type: 'object' }, { type: 'array', items: { type: 'object' } }],
|
|
1570
1603
|
description: 'Chained parametric warp(s): { dx, dy } expressions of x, y, t — one object or an array',
|
|
@@ -1696,6 +1729,75 @@ EXAMPLES:
|
|
|
1696
1729
|
required: ['action'],
|
|
1697
1730
|
},
|
|
1698
1731
|
},
|
|
1732
|
+
{
|
|
1733
|
+
name: 'pinepaper_accessibility_check',
|
|
1734
|
+
annotations: {
|
|
1735
|
+
title: 'Accessibility Check',
|
|
1736
|
+
readOnlyHint: true,
|
|
1737
|
+
destructiveHint: false,
|
|
1738
|
+
idempotentHint: true,
|
|
1739
|
+
openWorldHint: false,
|
|
1740
|
+
},
|
|
1741
|
+
description: `Check the live scene for two accessibility failures that stop ads and broadcast spots: low text contrast and flashing.
|
|
1742
|
+
|
|
1743
|
+
- contrast: WCAG 1.4.3 AA — 4.5:1, or 3:1 for large text (24 px, or 18.66 px bold). Each text's fill against the solid fill of the topmost item under it, or the page background. Text over a photo or gradient is skipped and listed.
|
|
1744
|
+
- flash: WCAG 2.3.1 general flash — samples the timeline (duration, fps) and fails any region of the frame with more than 3 flashes in a second. It seeks deterministically, so playback is not disturbed.
|
|
1745
|
+
|
|
1746
|
+
Both are heuristic screens and say so: not a certified photosensitive-epilepsy (PSE) test, and red flashes are not checked. Needs a live studio for verdicts; in code-only mode it returns the measuring code.
|
|
1747
|
+
|
|
1748
|
+
EXAMPLE: { checks: ["contrast", "flash"], duration: 6, fps: 20 }`,
|
|
1749
|
+
inputSchema: {
|
|
1750
|
+
type: 'object',
|
|
1751
|
+
properties: {
|
|
1752
|
+
checks: { type: 'array', items: { type: 'string', enum: ['contrast', 'flash'] }, description: 'Which checks (default both).' },
|
|
1753
|
+
duration: { type: 'number', exclusiveMinimum: 0, maximum: 120, description: 'flash: seconds of the timeline to sample (default 5).' },
|
|
1754
|
+
fps: { type: 'integer', minimum: 10, maximum: 60, description: 'flash: samples per second (default 20).' },
|
|
1755
|
+
},
|
|
1756
|
+
},
|
|
1757
|
+
},
|
|
1758
|
+
{
|
|
1759
|
+
name: 'pinepaper_render_batch',
|
|
1760
|
+
annotations: {
|
|
1761
|
+
title: 'Render Batch',
|
|
1762
|
+
readOnlyHint: false,
|
|
1763
|
+
destructiveHint: false,
|
|
1764
|
+
idempotentHint: false,
|
|
1765
|
+
openWorldHint: false,
|
|
1766
|
+
},
|
|
1767
|
+
description: `Render one creative per row of data from the current scene: for each row, apply its changes (modify_item per item, and / or a template's params), then export with the shared export options. For variants, translations and dynamic creative (DCO).
|
|
1768
|
+
|
|
1769
|
+
Each row runs through the same tools you would call yourself, so every check applies per row: unread properties, text fit, export fidelity. A row that fails is reported by index and the batch continues.
|
|
1770
|
+
|
|
1771
|
+
ROWS DO NOT RESET: row 2 starts from the scene row 1 left. Give every row every field that varies (all headlines, all prices), not only what differs from the row before. Pair with text fit so longer copy still fits its box, and read each row's fidelity for text_overflow.
|
|
1772
|
+
|
|
1773
|
+
Needs a live studio (it renders); in code-only mode it is refused. Max 100 rows; estimateOnly checks the rows first.
|
|
1774
|
+
|
|
1775
|
+
EXAMPLE:
|
|
1776
|
+
{ rows: [
|
|
1777
|
+
{ id: "en", changes: { headline: { content: "Summer Sale" }, price: { content: "$19" } } },
|
|
1778
|
+
{ id: "de", changes: { headline: { content: "Sommerschlussverkauf" }, price: { content: "19 €" } } } ],
|
|
1779
|
+
export: { format: "png", platform: "instagram" } }`,
|
|
1780
|
+
inputSchema: {
|
|
1781
|
+
type: 'object',
|
|
1782
|
+
properties: {
|
|
1783
|
+
rows: {
|
|
1784
|
+
type: 'array', minItems: 1, maxItems: 100,
|
|
1785
|
+
description: 'One entry per creative.',
|
|
1786
|
+
items: {
|
|
1787
|
+
type: 'object',
|
|
1788
|
+
properties: {
|
|
1789
|
+
id: { type: 'string', description: 'A name for the row, echoed in its result.' },
|
|
1790
|
+
changes: { type: 'object', additionalProperties: { type: 'object' }, description: 'itemId → properties (as modify_item takes them).' },
|
|
1791
|
+
template: { type: 'object', properties: { templateId: { type: 'string' }, params: { type: 'object' } }, required: ['templateId', 'params'], description: 'template_params apply for this row.' },
|
|
1792
|
+
},
|
|
1793
|
+
},
|
|
1794
|
+
},
|
|
1795
|
+
export: { type: 'object', description: 'pinepaper_agent_export options for every row (format, platform, quality, duration, ad, …).' },
|
|
1796
|
+
estimateOnly: { type: 'boolean', description: 'Validate and return the plan without rendering.' },
|
|
1797
|
+
},
|
|
1798
|
+
required: ['rows', 'export'],
|
|
1799
|
+
},
|
|
1800
|
+
},
|
|
1699
1801
|
{
|
|
1700
1802
|
name: 'pinepaper_camera_director',
|
|
1701
1803
|
annotations: {
|
|
@@ -1942,7 +2044,7 @@ EXAMPLE: { times: [0, 0.5, 1, 1.5, 2], seed: 42 }`,
|
|
|
1942
2044
|
|
|
1943
2045
|
ACTIONS:
|
|
1944
2046
|
- upload_video: { url (required), position?, scale?, timeOffset? (timeline start, s), clipInPoint?, clipOutPoint? (trim, s) } → { id, duration, width, height, name }
|
|
1945
|
-
- upload_audio: { url (required), volume? (0–1, default 1), loop
|
|
2047
|
+
- upload_audio: { url (required), volume? (0–1, default 1), loop?, muted?, timeOffset? } → { id, registryId, duration, name }. Pass loop EXPLICITLY — loop:true for a music bed, loop:false for a voice-over or sting; when omitted, the studio's own default applies and differs between builds.
|
|
1946
2048
|
- list: → all media [{ id, kind, duration, … }]
|
|
1947
2049
|
- remove: { id } → removed boolean
|
|
1948
2050
|
- set_playback_rate: { id, rate (0.25–4) } → applies to video or audio
|
|
@@ -1980,7 +2082,7 @@ EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, time
|
|
|
1980
2082
|
strength: { type: 'number', description: 'apply_track_matte: 0–1.' },
|
|
1981
2083
|
hideMatte: { type: 'boolean', description: 'apply_track_matte: hide the matte item after applying.' },
|
|
1982
2084
|
live: { type: 'boolean', description: 'apply_track_matte: keep re-cutting as the matte moves/animates — the kinetic mask reveal. Omitted = bake once, destructively.' },
|
|
1983
|
-
url: { type: 'string', description:
|
|
2085
|
+
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." },
|
|
1984
2086
|
id: { type: 'string', description: 'Media id — required for remove / set_playback_rate / set_clip.' },
|
|
1985
2087
|
rate: { type: 'number', description: 'Playback rate 0.25–4 — set_playback_rate.' },
|
|
1986
2088
|
inPoint: { type: 'number', description: 'Clip in-point (media-time s) — set_clip.' },
|
|
@@ -1991,7 +2093,7 @@ EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, time
|
|
|
1991
2093
|
clipInPoint: { type: 'number', description: 'Trim in-point (s) — upload_video.' },
|
|
1992
2094
|
clipOutPoint: { type: 'number', description: 'Trim out-point (s) — upload_video.' },
|
|
1993
2095
|
volume: { type: 'number', description: 'Volume 0–1 (default 1) — upload_audio.' },
|
|
1994
|
-
loop: { type: 'boolean', description: '
|
|
2096
|
+
loop: { type: 'boolean', description: 'upload_audio: true = loop (music beds), false = play once (voice-over, stings). Pass it explicitly; the default when omitted is the studio\'s and differs between builds.' },
|
|
1995
2097
|
muted: { type: 'boolean', description: 'Start muted — upload_audio.' },
|
|
1996
2098
|
},
|
|
1997
2099
|
required: ['action'],
|
|
@@ -2300,9 +2402,9 @@ RECIPE — assemble-from-pieces: shatter with keepSource, keyframe each tile fro
|
|
|
2300
2402
|
|
|
2301
2403
|
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.
|
|
2302
2404
|
|
|
2303
|
-
Layer tags map onto the exact role tokens the expresses presets read (eye_left, pupil_right, mouth…), so
|
|
2405
|
+
Layer tags map onto the exact role tokens the expresses presets read (eye_left, pupil_right, mouth…), 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.
|
|
2304
2406
|
|
|
2305
|
-
Params: { info
|
|
2407
|
+
Params: { info — the decomposer manifest, whose parts live under info.parts (or info.tag2pinfo) as an OBJECT KEYED BY TAG, not a layers[] array: { eye_left: { 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? }
|
|
2306
2408
|
Returns: { groupId, parts, roles, rolesWired, warnings }
|
|
2307
2409
|
|
|
2308
2410
|
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.`,
|
|
@@ -2486,7 +2588,7 @@ Three of these refuse quietly in the engine: exportGLB with no perspective objec
|
|
|
2486
2588
|
type: 'object',
|
|
2487
2589
|
properties: {
|
|
2488
2590
|
action: { type: 'string', enum: ['export_lottie', 'export_dotlottie', 'import_lottie', 'export_glb', 'export_bvh', 'export_png_sequence'], description: 'Which interchange operation.' },
|
|
2489
|
-
options: { type: 'object', description:
|
|
2591
|
+
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." },
|
|
2490
2592
|
data: { anyOf: [{ type: 'string' }, { type: 'object' }], description: 'import_lottie: the Lottie JSON — object, JSON string, or URL.' },
|
|
2491
2593
|
skeletonId: { type: 'string', description: "export_bvh: which skeleton to write. A BVH is one skeleton's motion." },
|
|
2492
2594
|
},
|
|
@@ -2518,7 +2620,8 @@ render_soundtrack mixes every placed sound to a WAV and writes it to a file. It
|
|
|
2518
2620
|
inputSchema: {
|
|
2519
2621
|
type: 'object',
|
|
2520
2622
|
properties: {
|
|
2521
|
-
action: { type: 'string', enum: ['list_instruments', 'list_percussion', 'list_sfx', 'play_tone', 'play_chord', 'chord_frequencies', 'play_percussion', 'play_sfx', 'play_spec', 'from_text', 'play_from_text', 'create', 'timbre_from_path', 'set_placement', 'remove', 'stop_all', 'define_instrument', 'define_percussion', 'define_sfx', 'render_soundtrack'], description: 'Which sound operation.' },
|
|
2623
|
+
action: { type: 'string', enum: ['list_instruments', 'list_percussion', 'list_sfx', 'play_tone', 'play_chord', 'chord_frequencies', 'play_percussion', 'play_sfx', 'play_spec', 'from_text', 'play_from_text', 'create', 'sequence', 'timbre_from_path', 'set_placement', 'remove', 'stop_all', 'define_instrument', 'define_percussion', 'define_sfx', 'render_soundtrack'], description: 'Which sound operation.' },
|
|
2624
|
+
cues: { type: 'array', description: "sequence: every cue in ONE call — [{t, spec|preset, note?, duration?, visual?}]. Each is created and placed at its own t, so a music bed is one call rather than one per cue. Failures come back by index in failed[], because a bed where three of a hundred cues did not sound is neither a success nor a failure.", items: { type: 'object' } },
|
|
2522
2625
|
partials: { type: 'array', items: { type: 'object' }, description: 'define_*: [{ h, amp }] — the harmonic table that IS the timbre. Required for define_instrument.' },
|
|
2523
2626
|
envelope: { type: 'object', description: 'define_*: { attack, decay, sustain, release } — seconds, except sustain which is a level 0-1.' },
|
|
2524
2627
|
gain: { type: 'number', description: 'define_*: output level 0-1 (0.8 instrument, 0.85 percussion, 0.65 sfx).' },
|
|
@@ -2650,7 +2753,7 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2650
2753
|
camera: { type: 'object', description: "set_camera: { mode: follow|fixed|orbit, target?, radius?, speed?, eye?, lookAt? }." },
|
|
2651
2754
|
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.' },
|
|
2652
2755
|
pathId: { type: 'string', description: 'extrude_path / lathe_path: the canvas path to turn into geometry. The path itself is unchanged.' },
|
|
2653
|
-
mesh: { type: 'object', description: 'extrude_path / lathe_path options: { id?, depth?, unitsPerPixel?, caps?, segments?, arc?, flatness?, x?, y?, z?, rotY?, rotYDegrees?, scale?, doubleSided? }. 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.' },
|
|
2756
|
+
mesh: { type: 'object', description: 'extrude_path / lathe_path options: { id?, depth?, unitsPerPixel?, 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.' },
|
|
2654
2757
|
meshId: { type: 'string', description: 'remove_mesh: the mesh id.' },
|
|
2655
2758
|
light: { type: 'object', description: 'add_light / set_light: { id?, x?, y?, z?, color?, intensity?, range? }. At most 8 point lights; the ninth is refused by name because the shader array is fixed-size.' },
|
|
2656
2759
|
lightId: { type: 'string', description: 'set_light / remove_light: the light id.' },
|
|
@@ -2951,6 +3054,11 @@ PARAMS: { eps? (bounds-match tolerance px, default 4), cap? (max suggestions, de
|
|
|
2951
3054
|
},
|
|
2952
3055
|
description: `Create a behavior relationship between two items. Relations are the PRIMARY way to add animation in PinePaper - they describe HOW items should behave relative to each other.
|
|
2953
3056
|
|
|
3057
|
+
MOVES_ALONG_PATH — ride a path (a hand tracing a stroke, a car on a road):
|
|
3058
|
+
- sourceId: the item that moves. The path comes from ONE of: targetId (or params.pathId) naming a PATH ITEM, sampled into points for you; params.path as points [[x, y], …]; or params.equation.
|
|
3059
|
+
- params.duration: seconds for one traversal (or pass speed directly, 1 ≈ 150 px/s). params.delay: seconds on the timeline before it starts; until then the item waits at the path start. params.offset: [dx, dy] added to the point on the path — set it to (centre - pen tip) so a hand image's TIP, not its centre, rides the stroke. closed (loop back to the start; default from the path item), phase (0–1 start point), easing (linear | easeIn | easeOut | easeInOut | sine | bounce | pingpong), signal (deterministic, frame-rate-invariant — prefer it for export).
|
|
3060
|
+
- With no path from any of those it is refused, not accepted as a still item.
|
|
3061
|
+
|
|
2954
3062
|
USE WHEN:
|
|
2955
3063
|
- "moon orbits earth" → relationType: orbits
|
|
2956
3064
|
- "label follows player" → relationType: follows
|
|
@@ -3016,7 +3124,7 @@ RELATION COMPATIBILITY:
|
|
|
3016
3124
|
Text→Text: font blend
|
|
3017
3125
|
Cross-type: particle denoising transition
|
|
3018
3126
|
- group_morphs_to: pair-by-index morph between two paper.Groups (any two — graph vertices+edges, letter collages, dashboard clusters). Path.Line children deform via endpoints; other children translate; excess children fade. Params: duration, hold, loop, easing (linear|easeIn|easeOut|easeInOut), deformLines.
|
|
3019
|
-
- moves_along_path: self-relation (targetId=null); item is driven along a path. Source the path EITHER from params.path (array of {x,y} or [x,y]) OR from params.equation (S10 B5) — a math curve the item TRAVERSES, sampled deterministically on the Expression IR: params.equation = { kind: function|parametric|fourier|preset, expr | xExpr/yExpr | harmonics | preset, min, max, samples, scale, cx, cy, flipY, warp } (same equation contract as pinepaper_equation_path, but origin is cx/cy, default 0). Params: path OR equation, speed, closed, phase, easing (linear|easeIn|easeOut|easeInOut|sine|bounce|pingpong).
|
|
3127
|
+
- moves_along_path: self-relation (targetId=null); item is driven along a path. Source the path EITHER from params.path (array of {x,y} or [x,y]) OR from params.equation (S10 B5) — a math curve the item TRAVERSES, sampled deterministically on the Expression IR: params.equation = { kind: function|parametric|fourier|preset, expr | xExpr/yExpr | harmonics | preset, min, max, samples, scale, cx, cy, flipY, warp } (same equation contract as pinepaper_equation_path, but origin is cx/cy, default 0). READ ONLY ON THE SIGNAL PATH: easing 'bounce' is piecewise and leaves it, which silently discards the equation and leaves the item where it started. Params: path OR equation, speed, closed, phase, easing (linear|easeIn|easeOut|easeInOut|sine|bounce|pingpong).
|
|
3020
3128
|
|
|
3021
3129
|
GEOMETRIC CONSTRUCTION CONSTRAINTS (Layer 2) — the source item is RE-DERIVED every frame from its anchor item(s), so dragging an anchor updates the dependent live (GeoGebra-style), and the construction persists as relation graph data (edit it by editing relations, not by re-running code). Anchor A is the relation target; extra anchors ride in params:
|
|
3022
3130
|
- is_midpoint_of: source = midpoint(target, params.other). params.other = id of the second endpoint (B).
|
|
@@ -3394,9 +3502,13 @@ ANIMATABLE PROPERTIES:
|
|
|
3394
3502
|
- opacity: 0-1
|
|
3395
3503
|
- fillColor: Color string
|
|
3396
3504
|
- strokeColor: Color string
|
|
3505
|
+
- strokeWidth, width, height, radius, shadowBlur, shadowColor: Numbers / colours
|
|
3397
3506
|
- fontSize: Number
|
|
3507
|
+
- STROKE DRAW-ON (paths): trimStart, trimEnd (0-1 of the path's length), trimOffset (shifts the visible window along it), dashArray. A draw-on is trimEnd 0 -> 1.
|
|
3508
|
+
The authoritative list for a given item is pinepaper_get_animatable_properties — ask it rather than guessing.
|
|
3398
3509
|
|
|
3399
|
-
EASING
|
|
3510
|
+
EASING — IT SHAPES THE SEGMENT ARRIVING AT THE KEYFRAME IT IS ON.
|
|
3511
|
+
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.
|
|
3400
3512
|
- linear: Constant speed
|
|
3401
3513
|
- easeIn: Slow start
|
|
3402
3514
|
- easeOut: Slow end
|
|
@@ -3432,6 +3544,7 @@ EXAMPLE — Reveal at 2s, skip first second of keyframe data, end at 4s of keyfr
|
|
|
3432
3544
|
easing: {
|
|
3433
3545
|
type: 'string',
|
|
3434
3546
|
enum: ['linear', 'easeIn', 'easeOut', 'easeInOut', 'bounce', 'elastic'],
|
|
3547
|
+
description: 'Shapes the segment ARRIVING at this keyframe (from the previous one). Ignored on the first keyframe.',
|
|
3435
3548
|
},
|
|
3436
3549
|
},
|
|
3437
3550
|
required: ['time', 'properties'],
|
|
@@ -3639,7 +3752,9 @@ USE WHEN:
|
|
|
3639
3752
|
- Creating complex reveal sequences
|
|
3640
3753
|
- Building custom transitions
|
|
3641
3754
|
|
|
3642
|
-
Requires maskType and keyframes array
|
|
3755
|
+
Requires maskType and keyframes array.
|
|
3756
|
+
|
|
3757
|
+
COORDINATES: a 'rectangle' mask's x / y are its TOP-LEFT corner (measured: {x:540, y:620, width:800, height:800} reads back as bounds [540, 620, 800, 800]) — unlike items, which are placed by their centre. Convert before masking an item you placed by centre.`,
|
|
3643
3758
|
inputSchema: {
|
|
3644
3759
|
type: 'object',
|
|
3645
3760
|
properties: {
|
|
@@ -3776,7 +3891,6 @@ USE WHEN:
|
|
|
3776
3891
|
- Building presentation zoom sequences
|
|
3777
3892
|
- Making dramatic reveals with camera motion
|
|
3778
3893
|
- Tour animations showing different parts of canvas
|
|
3779
|
-
- 3D perspective effects (pitch/yaw tilt via equirectangular projection)
|
|
3780
3894
|
|
|
3781
3895
|
The camera follows a keyframe sequence. For an orbit-style camera (revolving
|
|
3782
3896
|
around a subject), add 4-8 keyframes around the pivot using pathMode: "arc"
|
|
@@ -3791,8 +3905,7 @@ KEYFRAME PROPERTIES:
|
|
|
3791
3905
|
'item-id' itemRegistry lookup → item bounds center (tracks the item as it moves)
|
|
3792
3906
|
{ item: 'id', offset: [dx, dy] } item center + offset
|
|
3793
3907
|
- center: [x, y] — legacy alias, honored only when focus is absent
|
|
3794
|
-
- pitch: 3D tilt in degrees
|
|
3795
|
-
- yaw: 3D rotation in degrees. Uses equirectangular projection
|
|
3908
|
+
- pitch / yaw: 3D tilt in degrees. Rendered by studio builds that support camera tilt; on others they are accepted with NO visible effect — the result's tiltRenders says which this studio is, and a note appears when a tilt would be invisible.
|
|
3796
3909
|
- easing: Timing function (linear, easeIn, easeOut, easeInOut, bounce, elastic)
|
|
3797
3910
|
|
|
3798
3911
|
CURVED INTER-KEYFRAME PATHS (optional; default is linear):
|
|
@@ -3844,8 +3957,8 @@ EXAMPLE (curved arc between waypoints):
|
|
|
3844
3957
|
items: { type: 'number' },
|
|
3845
3958
|
description: 'Legacy view center [x, y] — honored only when focus is absent',
|
|
3846
3959
|
},
|
|
3847
|
-
pitch: { type: 'number', description: '3D tilt in degrees
|
|
3848
|
-
yaw: { type: 'number', description: '3D rotation in degrees' },
|
|
3960
|
+
pitch: { type: 'number', description: '3D tilt in degrees — rendered where the studio supports camera tilt (result.tiltRenders).' },
|
|
3961
|
+
yaw: { type: 'number', description: '3D rotation in degrees — rendered where the studio supports camera tilt (result.tiltRenders).' },
|
|
3849
3962
|
easing: {
|
|
3850
3963
|
type: 'string',
|
|
3851
3964
|
enum: ['linear', 'easeIn', 'easeOut', 'easeInOut', 'bounce', 'elastic'],
|
|
@@ -3882,12 +3995,12 @@ EXAMPLE (curved arc between waypoints):
|
|
|
3882
3995
|
},
|
|
3883
3996
|
},
|
|
3884
3997
|
},
|
|
3885
|
-
duration: { type: 'number', description: 'Total animation duration in seconds' },
|
|
3998
|
+
duration: { type: 'number', description: 'Total animation duration in seconds. Optional: omitted, the camera runs to its last keyframe.' },
|
|
3886
3999
|
loop: { type: 'boolean', description: 'Loop the animation' },
|
|
3887
4000
|
delay: { type: 'number', description: 'Delay before animation starts' },
|
|
3888
4001
|
fov: { type: 'number', description: 'Field of view in degrees for 3D perspective (default: 60)' },
|
|
3889
4002
|
},
|
|
3890
|
-
required: ['keyframes'
|
|
4003
|
+
required: ['keyframes'],
|
|
3891
4004
|
},
|
|
3892
4005
|
},
|
|
3893
4006
|
{
|
|
@@ -4026,8 +4139,8 @@ SUPPORTED ANIMATIONS: bounce, breathe, fade, glow, jelly, path, pulse, rotate, s
|
|
|
4026
4139
|
},
|
|
4027
4140
|
itemType: {
|
|
4028
4141
|
type: 'string',
|
|
4029
|
-
enum: [
|
|
4030
|
-
description: 'Type of item to create',
|
|
4142
|
+
enum: [...ItemTypeSchema.options],
|
|
4143
|
+
description: 'Type of item to create. Every type the engine has — this used to advertise ten while the tool hand-built four and turned the rest into a small circle.',
|
|
4031
4144
|
},
|
|
4032
4145
|
position: {
|
|
4033
4146
|
type: 'object',
|
|
@@ -4315,7 +4428,7 @@ EXAMPLE:
|
|
|
4315
4428
|
},
|
|
4316
4429
|
description: `Shape a delay across many items. What makes a stagger read as designed rather than as a queue is where it STARTS and how it SPREADS — a grid lighting up outward from the centre, a row converging from both edges.
|
|
4317
4430
|
|
|
4318
|
-
Delays
|
|
4431
|
+
Delays go where each kind of animation reads them: loop presets take animationDelay, and an item that already has a KEYFRAME track has that track's start offset (timeOffset) shifted by its delay — so apply the stagger after the keyframes, and re-applying replaces the shift rather than adding to it. The result's shiftedKeyframeTracks says how many tracks moved.
|
|
4319
4432
|
|
|
4320
4433
|
ACTIONS:
|
|
4321
4434
|
- apply: write the delays onto real items, in the order you list them (row-major for a grid).
|
|
@@ -4782,7 +4895,7 @@ Both actions return a WCAG contrast audit, so you can see failing pairs before s
|
|
|
4782
4895
|
ACTIONS:
|
|
4783
4896
|
- define: Turn items into a component master. Params: itemIds[], name
|
|
4784
4897
|
- list: All defined components
|
|
4785
|
-
- instantiate: Place an instance. Params: componentId, position, overrides
|
|
4898
|
+
- instantiate: Place an instance. Params: componentId, position, overrides → { instanceId, parts: [{key, type, content?}], orphaned }. overrides is { <componentKey>: { <prop>: value } }, e.g. { "ck_1": { "content": "50% OFF" }, "ck_2": { "fillColor": "#e11d48" } } — keys come from list (each component's parts) or from a previous instantiate.
|
|
4786
4899
|
- set_override: Change ONE part of ONE instance without detaching it. Params: instanceId, componentKey, prop, value
|
|
4787
4900
|
- sync: Push the master's current state to every instance (overrides are preserved). Params: componentId
|
|
4788
4901
|
- update_from_instance: Promote an instance's edits back into the master. Params: instanceId
|
|
@@ -4810,7 +4923,7 @@ Overrides are the point: an instance can differ from its master and still receiv
|
|
|
4810
4923
|
anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }, { type: 'object' }, { type: 'array' }],
|
|
4811
4924
|
description: 'Override value',
|
|
4812
4925
|
},
|
|
4813
|
-
overrides: { type: 'object', description: '
|
|
4926
|
+
overrides: { type: 'object', description: 'instantiate: { <componentKey>: { <prop>: value } } — keys are listed in parts by list and instantiate. A key the component does not have is reported in orphaned, not applied silently.' },
|
|
4814
4927
|
},
|
|
4815
4928
|
required: ['action'],
|
|
4816
4929
|
},
|
|
@@ -4955,7 +5068,7 @@ node survives none of them.`,
|
|
|
4955
5068
|
type: 'object',
|
|
4956
5069
|
properties: {
|
|
4957
5070
|
action: { type: 'string', enum: ['analyze', 'animate_to_beat'] },
|
|
4958
|
-
source: { type: 'string', description: 'Asset id, data URL, or
|
|
5071
|
+
source: { type: 'string', description: 'Asset id, data URL, URL — or an uploaded clip\'s id / registryId from pinepaper_media upload_audio / upload_video.' },
|
|
4959
5072
|
itemId: { type: 'string' },
|
|
4960
5073
|
beats: { type: 'array', items: { type: 'number' }, description: 'Beat times (s) you already have' },
|
|
4961
5074
|
grid: { type: 'boolean', description: 'Even pulse from the tempo instead of raw onsets' },
|
|
@@ -5045,26 +5158,28 @@ ACTIONS:
|
|
|
5045
5158
|
|
|
5046
5159
|
Available filters (GPU raster set):
|
|
5047
5160
|
- Color: grayscale, sepia, brightness, contrast, saturation, invert, posterize (levels), hsl (hue/saturation/lightness), colorTint (color, intensity, blendMode), colorMatrix (matrix), duotone (shadow, highlight, mix), paletteMap (swatches[], amount, preserveShading — recolor to a fixed palette)
|
|
5048
|
-
- Stylize: vignette (intensity, radius), edgeDetect, halftoneDots (size, angle), halftoneCMYK (size), dither (levels), grain (amount, seed), scanlines (intensity, period), chromaticAberration (amount), blur
|
|
5161
|
+
- Stylize: vignette (intensity, radius), edgeDetect, halftoneDots (size, angle), halftoneCMYK (size), dither (levels), grain (amount, seed), scanlines (intensity, period), chromaticAberration (amount), blur (radius)
|
|
5049
5162
|
- Light (additive): bloom (amount, threshold, radius), glow, halation (warm-fringed bloom; color), lightShafts (amount, threshold, decay, density, x, y, color — volumetric rays from a point)
|
|
5050
5163
|
- Keying: lumaKey (threshold, softness, invert)
|
|
5051
5164
|
- SECOND-INPUT (params.map = another item's id, resolved to its pixels): displace (amount, dispersion, map, mapChannel — the map's brightness pushes pixels), refract (displace with per-channel dispersion — glass), trackMatte (channel, invert, strength — prefer pinepaper_media apply_track_matte for the live version), datamosh (amount, block, map? — self-moshes without a map)
|
|
5052
5165
|
|
|
5053
5166
|
RECIPE — grunge poster: chain [grain, scanlines, vignette]. Glass header: refract with map = a gradient item.
|
|
5167
|
+
HARD-EDGED LOOKS NEED SMOOTHING OFF: posterize, dither, halftone and pixel looks are exact at 1:1, but a raster that is then scaled, skewed or rotated is RESAMPLED with smoothing, which blends the flat levels back into a gradient (measured: posterize then skew → 256 tones again). Set smoothing 'off' on the image (pinepaper_import_image, or pinepaper_modify_item {smoothing: 'off'}) to keep the levels.
|
|
5168
|
+
CHAIN ORDER MATTERS: each filter runs on the previous one's output. duotone then halftoneDots comes out grey (the ink colour is lost); halftoneDots then duotone keeps it. If a chain loses its colour, move the colour-mapping filter (duotone, paletteMap, colorTint) to the end.
|
|
5054
5169
|
(Scene-wide filters — sharpen/emboss/noise/vintage etc. — are a different surface: use pinepaper_add_filter.)`,
|
|
5055
5170
|
inputSchema: {
|
|
5056
5171
|
type: 'object',
|
|
5057
5172
|
properties: {
|
|
5058
5173
|
action: {
|
|
5059
5174
|
type: 'string',
|
|
5060
|
-
enum: ['apply', 'chain'],
|
|
5175
|
+
enum: ['apply', 'chain', 'analyze_palette', 'recolor_palette'],
|
|
5061
5176
|
description: 'Filter action',
|
|
5062
5177
|
},
|
|
5063
5178
|
itemId: { type: 'string', description: 'Target raster item ID' },
|
|
5064
5179
|
filterName: { type: 'string', description: 'Filter name (for apply)' },
|
|
5065
5180
|
params: {
|
|
5066
5181
|
type: 'object',
|
|
5067
|
-
description:
|
|
5182
|
+
description: "Filter parameters, named per filter in the list above. Blur takes {radius: 5} — a misspelled parameter is ignored rather than refused, so check the name. For blur only, 'amount' is accepted as an alias for radius because this example used to say so; every other filter that lists amount reads amount.",
|
|
5068
5183
|
},
|
|
5069
5184
|
filters: {
|
|
5070
5185
|
type: 'array',
|
|
@@ -5076,8 +5191,18 @@ RECIPE — grunge poster: chain [grain, scanlines, vignette]. Glass header: refr
|
|
|
5076
5191
|
},
|
|
5077
5192
|
required: ['name'],
|
|
5078
5193
|
},
|
|
5079
|
-
description: 'Filter chain (for chain action)',
|
|
5194
|
+
description: 'Filter chain (for chain action). Order matters — see CHAIN ORDER above.',
|
|
5080
5195
|
},
|
|
5196
|
+
maxSwatches: { type: 'integer', minimum: 1, maximum: 32, description: 'analyze_palette: swatch count cap (default 8).' },
|
|
5197
|
+
mapping: {
|
|
5198
|
+
oneOf: [
|
|
5199
|
+
{ type: 'array', items: { type: 'object', properties: { from: { type: 'string' }, to: { type: 'string' } }, required: ['from', 'to'] } },
|
|
5200
|
+
{ type: 'object', additionalProperties: { type: 'string' } },
|
|
5201
|
+
],
|
|
5202
|
+
description: "recolor_palette: {from,to}[] or {'#old':'#new'} — colours swap, shading survives.",
|
|
5203
|
+
},
|
|
5204
|
+
amount: { type: 'number', minimum: 0, maximum: 1, description: 'recolor_palette: blend 0–1. (A filter\'s own amount goes in params.)' },
|
|
5205
|
+
preserveShading: { type: 'number', minimum: 0, maximum: 1, description: 'recolor_palette: how much of the original luminance detail survives (default 1).' },
|
|
5081
5206
|
},
|
|
5082
5207
|
required: ['action', 'itemId'],
|
|
5083
5208
|
},
|
|
@@ -5091,20 +5216,28 @@ RECIPE — grunge poster: chain [grain, scanlines, vignette]. Glass header: refr
|
|
|
5091
5216
|
idempotentHint: false,
|
|
5092
5217
|
openWorldHint: false,
|
|
5093
5218
|
},
|
|
5094
|
-
description: `
|
|
5219
|
+
description: `Cut a region out of an image along a polygon — the subject cutout behind 2.5D parallax, stickers and collage.
|
|
5095
5220
|
|
|
5096
5221
|
ACTIONS:
|
|
5097
|
-
-
|
|
5098
|
-
- apply:
|
|
5222
|
+
- cut: { itemId, points: [[x, y], …] (canvas coordinates, at least 3 vertices, in order around the subject), keepOriginal? (default true) } → { cutoutId, originalId, originalKept }. The cutout is a NEW image item, placed exactly over the region it came from, transparent outside the polygon. keepOriginal:false consumes the source image (the studio's own lasso deletes it); the default keeps it, so you can layer the cutout over the photo.
|
|
5223
|
+
- activate / apply: the interactive, mouse-driven lasso. Refused over MCP — use cut.
|
|
5224
|
+
|
|
5225
|
+
Vertices closer than 12 px to the FIRST vertex are skipped (the lasso reads a click there as "close the shape"), so trace with points at least that far from where you started.`,
|
|
5099
5226
|
inputSchema: {
|
|
5100
5227
|
type: 'object',
|
|
5101
5228
|
properties: {
|
|
5102
5229
|
action: {
|
|
5103
5230
|
type: 'string',
|
|
5104
|
-
enum: ['activate', 'apply'],
|
|
5105
|
-
description: 'Lasso action',
|
|
5231
|
+
enum: ['cut', 'activate', 'apply'],
|
|
5232
|
+
description: 'Lasso action — cut is the one that works over MCP',
|
|
5233
|
+
},
|
|
5234
|
+
itemId: { type: 'string', description: 'The image item to cut from' },
|
|
5235
|
+
points: {
|
|
5236
|
+
type: 'array',
|
|
5237
|
+
description: 'cut: polygon vertices in canvas coordinates, [x, y] or {x, y}, at least 3',
|
|
5238
|
+
items: { anyOf: [{ type: 'array', items: { type: 'number' }, minItems: 2, maxItems: 2 }, { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } }, required: ['x', 'y'] }] },
|
|
5106
5239
|
},
|
|
5107
|
-
|
|
5240
|
+
keepOriginal: { type: 'boolean', description: 'cut: keep the source image (default true). false = the source is consumed.' },
|
|
5108
5241
|
},
|
|
5109
5242
|
required: ['action'],
|
|
5110
5243
|
},
|
|
@@ -5536,6 +5669,9 @@ Params: title (page title), download (bool)`,
|
|
|
5536
5669
|
type: 'object',
|
|
5537
5670
|
properties: {
|
|
5538
5671
|
title: { type: 'string', description: 'HTML page title (default: PinePaper Widget)' },
|
|
5672
|
+
lang: { type: 'string', description: 'BCP 47 language tag for the page (default en), e.g. "ar", "hi", "zh-Hans".' },
|
|
5673
|
+
dir: { type: 'string', enum: ['ltr', 'rtl', 'auto'], description: 'Text direction for the page — "rtl" for Arabic / Hebrew.' },
|
|
5674
|
+
alt: { type: 'string', description: 'Text alternative for the animation: the canvas gets role="img" and this as its aria-label, and the page a meta description. Screen readers otherwise announce nothing.' },
|
|
5539
5675
|
download: { type: 'boolean', description: 'Trigger browser download (default: false)' },
|
|
5540
5676
|
},
|
|
5541
5677
|
},
|
|
@@ -6441,6 +6577,7 @@ ACTIONS:
|
|
|
6441
6577
|
|
|
6442
6578
|
ACTIONS:
|
|
6443
6579
|
- enable — { rotation?: [lambda,phi,gamma], momentum?, showOcean? } switch to globe mode
|
|
6580
|
+
- disable — {} leave globe mode: stops spin, drag and rotation (reload the map for a flat projection)
|
|
6444
6581
|
- rotate_to — { lon, lat, duration? } versor-slerp the globe to face a coordinate
|
|
6445
6582
|
- spin — { speed?, axis?: "longitude"|"latitude", duration? } continuous auto-rotation
|
|
6446
6583
|
- world_tour — { regions?: string[] | coords?: [lon,lat][], dwell?, travel?, tilt?, loop?, easing?, highlightColors?: string[], showLabels?, labelColor? } the headline: the globe swings to each stop, highlighting it (requires regions OR coords)
|
|
@@ -6450,7 +6587,7 @@ ACTIONS:
|
|
|
6450
6587
|
inputSchema: {
|
|
6451
6588
|
type: 'object',
|
|
6452
6589
|
properties: {
|
|
6453
|
-
action: { type: 'string', enum: ['enable', 'rotate_to', 'spin', 'world_tour', 'stop_tour', 'pin_item', 'tour_item'], description: 'Globe action' },
|
|
6590
|
+
action: { type: 'string', enum: ['enable', 'disable', 'rotate_to', 'spin', 'world_tour', 'stop_tour', 'pin_item', 'tour_item'], description: 'Globe action' },
|
|
6454
6591
|
rotation: { type: 'array', items: { type: 'number' }, description: 'Initial rotation [lambda, phi, gamma] (enable)' },
|
|
6455
6592
|
momentum: { type: 'boolean', description: 'Drag-spin inertia (enable)' },
|
|
6456
6593
|
showOcean: { type: 'boolean', description: 'Render ocean sphere (enable)' },
|
|
@@ -6494,7 +6631,8 @@ ACTIONS:
|
|
|
6494
6631
|
Most of this tool is glyph authoring — drawing a typeface. If you just want to know what font families are available to set on a text item, that is ONE action:
|
|
6495
6632
|
|
|
6496
6633
|
- list_available — { category?, loadedOnly? } every family the studio can render, with whether its file is loaded yet
|
|
6497
|
-
-
|
|
6634
|
+
- load — { name } load a family NOW: the studio's catalogue first, then Google Fonts (e.g. Monoton). Returns available + via. Text tools also load on first use, so this is for checking up front.
|
|
6635
|
+
- check — { name, text? } is this family really there, and can it draw THIS string? "available: false" means NOT LOADED YET — the result says whether it is loadable; use load. Pass the text: a font with four glyphs is installed and will still render most characters in the fallback face, and that second answer is the one that decides the pixels. Do NOT rely on document.fonts.check() — it returns true for a family that does not exist.
|
|
6498
6636
|
- fallbacks — {} which text items on the canvas are silently drawing in something other than the family they asked for
|
|
6499
6637
|
|
|
6500
6638
|
ACTIONS (authoring):
|
|
@@ -6520,7 +6658,7 @@ ACTIONS (authoring):
|
|
|
6520
6658
|
action: {
|
|
6521
6659
|
type: 'string',
|
|
6522
6660
|
enum: [
|
|
6523
|
-
'list_available', 'check', 'fallbacks',
|
|
6661
|
+
'list_available', 'check', 'load', 'fallbacks',
|
|
6524
6662
|
'show_studio', 'set_name', 'get_required_chars', 'get_status',
|
|
6525
6663
|
'create_glyph', 'create_space', 'remove_glyph', 'set_metrics',
|
|
6526
6664
|
'export', 'load_into_document', 'export_data', 'import_data',
|
|
@@ -6531,7 +6669,8 @@ ACTIONS (authoring):
|
|
|
6531
6669
|
text: { type: 'string', description: 'check: the string you intend to draw. Without it you learn only whether the family is installed, not whether it has glyphs for your characters.' },
|
|
6532
6670
|
category: { type: 'string', description: 'list_available: narrow to one category (e.g. display, serif, mono).' },
|
|
6533
6671
|
loadedOnly: { type: 'boolean', description: 'list_available: only families whose font file has already loaded. Unloaded ones still render once the studio fetches them.' },
|
|
6534
|
-
name: { type: 'string', description: 'Font
|
|
6672
|
+
name: { type: 'string', description: 'Font family (check, load) or the font being built (set_name). family / fontFamily are accepted for it.' },
|
|
6673
|
+
family: { type: 'string', description: 'Alias of name, for check / load.' },
|
|
6535
6674
|
set: { type: 'string', description: 'Character set name (get_required_chars; default "minimum")' },
|
|
6536
6675
|
character: { type: 'string', description: 'Single-character glyph identifier (create_glyph, remove_glyph)' },
|
|
6537
6676
|
pathId: { type: 'string', description: 'Item id of the path source (create_glyph, remove_overlap, correct_direction, cleanup_path)' },
|
|
@@ -6794,7 +6933,7 @@ New — Geometric:
|
|
|
6794
6933
|
- drawGeometricAbstract: Mixed translucent shapes (colors, shapeCount, blendMode, rotation)
|
|
6795
6934
|
|
|
6796
6935
|
New — Particles:
|
|
6797
|
-
- drawWindField: Directional wind particles with noise turbulence (particleCount, colors,
|
|
6936
|
+
- drawWindField: Directional wind particles with noise turbulence (particleCount, colors, windDirection (degrees), windSpeed, turbulence, trailLength, trailWidth, opacity, bgColor; direction / speed are accepted for windDirection / windSpeed)
|
|
6798
6937
|
|
|
6799
6938
|
New — Textures:
|
|
6800
6939
|
- drawNoiseTexture: Perlin/grain/stipple noise (noiseType: perlin|grain|stipple, colors, scale, density, animated)
|
|
@@ -6847,14 +6986,19 @@ COMMON PARAMS (all generators):
|
|
|
6847
6986
|
},
|
|
6848
6987
|
region: {
|
|
6849
6988
|
type: 'object',
|
|
6850
|
-
description: 'Optional sub-region {x, y, width, height} (canvas coords) — generator draws into a clipped, translated sub-group instead of full-canvas. Region runs imply preserve (won\'t wipe the canvas)
|
|
6989
|
+
description: 'Optional sub-region {x, y, width, height} (canvas coords) — generator draws into a clipped, translated sub-group instead of full-canvas. Region runs imply preserve (won\'t wipe the canvas). An instance is the generator plus its box, or an explicit region.id: re-running with the same box (or id) replaces it, and the same generator in a different box adds another — so one generator several times, or several generators side by side, is regions. Older studios keep one region per generator (a second box replaces the first). Invalid/zero-size regions fall back to full-canvas. shape clips the region to a rounded rect, ellipse, circle, polygon or star, or to an existing path ({itemId}: x/y/width/height may then be omitted and default to its bounds). A studio without region shapes says so in the result (regionShapeApplied: false). On older studios, some generators also paint a page-wide background when confined to a region.',
|
|
6851
6990
|
properties: {
|
|
6852
6991
|
x: { type: 'number', description: 'Region top-left X in canvas coords' },
|
|
6853
6992
|
y: { type: 'number', description: 'Region top-left Y in canvas coords' },
|
|
6854
6993
|
width: { type: 'number', minimum: 1, description: 'Region width in pixels' },
|
|
6855
6994
|
height: { type: 'number', minimum: 1, description: 'Region height in pixels' },
|
|
6995
|
+
shape: { anyOf: [{ type: 'string', enum: ['rect', 'roundedRect', 'ellipse', 'circle', 'polygon', 'star'] }, { type: 'object', properties: { itemId: { type: 'string' } }, required: ['itemId'] }], description: "Clip shape (default 'rect'), or {itemId} to clip to that path's geometry." },
|
|
6996
|
+
radius: { type: 'number', minimum: 0, description: 'roundedRect corner radius (default 20% of the shorter side).' },
|
|
6997
|
+
sides: { type: 'integer', minimum: 3, maximum: 64, description: 'polygon sides (default 6).' },
|
|
6998
|
+
points: { type: 'integer', minimum: 3, maximum: 64, description: 'star points (default 5).' },
|
|
6999
|
+
innerRatio: { type: 'number', minimum: 0.05, maximum: 0.95, description: 'star inner radius / outer (default 0.5).' },
|
|
7000
|
+
id: { type: 'string', minLength: 1, maxLength: 64, description: 'Names this region instance, so re-running under the same id replaces it even if its box moved.' },
|
|
6856
7001
|
},
|
|
6857
|
-
required: ['x', 'y', 'width', 'height'],
|
|
6858
7002
|
},
|
|
6859
7003
|
},
|
|
6860
7004
|
required: ['generatorName'],
|
|
@@ -6986,13 +7130,14 @@ AVAILABLE FILTERS:
|
|
|
6986
7130
|
- blur: Gaussian blur effect (params: radius 0-20)
|
|
6987
7131
|
- brightness: Adjust brightness (params: value -100 to 100)
|
|
6988
7132
|
- contrast: Adjust contrast (params: value -100 to 100)
|
|
6989
|
-
-
|
|
7133
|
+
- saturation: Adjust color saturation (params: value -100 to 100). 'saturate' is accepted as the same filter.
|
|
6990
7134
|
- invert: Invert colors (params: intensity 0-1)
|
|
6991
7135
|
- noise: Add film grain (params: intensity 0-100, monochrome: true/false)
|
|
6992
7136
|
- vignette: Darken edges (params: intensity 0-1, radius 0-1)
|
|
6993
7137
|
- vintage: Preset combining sepia, vignette, noise
|
|
6994
7138
|
- colorOverlay: Add color tint (params: color, intensity, blendMode)
|
|
6995
7139
|
- sharpen: Increase sharpness (params: intensity 0-100)
|
|
7140
|
+
- emboss: 3D emboss (params: intensity 0-1)
|
|
6996
7141
|
- posterize: Reduce color levels (params: levels 2-32)
|
|
6997
7142
|
|
|
6998
7143
|
EXAMPLES:
|
|
@@ -7007,7 +7152,7 @@ Filters can be stacked - call multiple times to combine effects.`,
|
|
|
7007
7152
|
properties: {
|
|
7008
7153
|
filterType: {
|
|
7009
7154
|
type: 'string',
|
|
7010
|
-
enum: ['grayscale', 'sepia', 'blur', 'brightness', 'contrast', '
|
|
7155
|
+
enum: ['grayscale', 'sepia', 'blur', 'brightness', 'contrast', 'saturation', 'invert', 'noise', 'vignette', 'vintage', 'colorOverlay', 'sharpen', 'emboss', 'posterize', 'saturate'],
|
|
7011
7156
|
description: 'Type of filter to apply',
|
|
7012
7157
|
},
|
|
7013
7158
|
params: {
|
|
@@ -7439,7 +7584,7 @@ WORKFLOW: start_job → batch_execute (everything in ONE call) → end_job (scre
|
|
|
7439
7584
|
|
|
7440
7585
|
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.
|
|
7441
7586
|
|
|
7442
|
-
CANVAS PRESETS: instagram (1080x1080), instagram-story (1080x1920), tiktok (1080x1920), youtube (1920x1080), youtube-thumbnail (1280x720), twitter (1200x675), linkedin (1200x627), web, print-a4, print-letter`,
|
|
7587
|
+
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)`,
|
|
7443
7588
|
inputSchema: {
|
|
7444
7589
|
type: 'object',
|
|
7445
7590
|
properties: {
|
|
@@ -7453,12 +7598,12 @@ CANVAS PRESETS: instagram (1080x1080), instagram-story (1080x1920), tiktok (1080
|
|
|
7453
7598
|
},
|
|
7454
7599
|
screenshotPolicy: {
|
|
7455
7600
|
type: 'string',
|
|
7456
|
-
enum: ['on_complete', 'on_error', '
|
|
7457
|
-
description:
|
|
7601
|
+
enum: ['on_complete', 'on_error', 'none', 'on_request', 'never'],
|
|
7602
|
+
description: "When to take screenshots (default: on_complete). 'never' is accepted as 'none'.",
|
|
7458
7603
|
},
|
|
7459
7604
|
canvasPreset: {
|
|
7460
7605
|
type: 'string',
|
|
7461
|
-
enum: ['instagram', 'instagram-story', 'tiktok', 'youtube', 'youtube-thumbnail', 'twitter', 'linkedin', 'web', 'print-a4', 'print-letter'],
|
|
7606
|
+
enum: ['instagram', 'instagram-story', 'tiktok', 'youtube', 'youtube-thumbnail', 'twitter', 'linkedin', 'web', 'print-a4', 'print-letter', 'print-a4-landscape', 'print-letter-landscape'],
|
|
7462
7607
|
description: 'Set canvas size to platform preset',
|
|
7463
7608
|
},
|
|
7464
7609
|
clearCanvas: {
|
|
@@ -7565,7 +7710,7 @@ EXAMPLES:
|
|
|
7565
7710
|
properties: {
|
|
7566
7711
|
canvasPreset: {
|
|
7567
7712
|
type: 'string',
|
|
7568
|
-
enum: ['instagram', 'instagram-story', 'tiktok', 'youtube', 'youtube-thumbnail', 'twitter', 'linkedin', 'web', 'print-a4', 'print-letter'],
|
|
7713
|
+
enum: ['instagram', 'instagram-story', 'tiktok', 'youtube', 'youtube-thumbnail', 'twitter', 'linkedin', 'web', 'print-a4', 'print-letter', 'print-a4-landscape', 'print-letter-landscape'],
|
|
7569
7714
|
description: 'Set canvas size to platform preset after reset',
|
|
7570
7715
|
},
|
|
7571
7716
|
backgroundColor: {
|
|
@@ -7594,7 +7739,7 @@ NOTE: For diagrams (flowcharts, UML), use pinepaper_create_diagram_shape + pinep
|
|
|
7594
7739
|
|
|
7595
7740
|
⚠️ EVERY operation executes LIVE on the canvas. Calling this twice DOUBLES all items. Call ONCE per pipeline.
|
|
7596
7741
|
|
|
7597
|
-
⚠️ SIZING: All positions/sizes MUST fit within the canvas. start_job returns canvasSize. Keep items within 5%-95% of canvas width/height. Max canvas:
|
|
7742
|
+
⚠️ SIZING: All positions/sizes MUST fit within the canvas. start_job returns canvasSize. Keep items within 5%-95% of canvas width/height. Max canvas: 8192x8192.
|
|
7598
7743
|
|
|
7599
7744
|
ONTOLOGY PREFLIGHT: Before code generation, this tool validates itemType, relationType, effectType, and generatorName against the canonical PinePaper vocabulary. A typo (e.g. "circel") returns a structured VALIDATION_ERROR with the operation index, the offending field, the canonical valid list, and a suggested fix — fix and retry rather than guessing. Pass skipValidation: true to bypass when intentionally using experimental vocabulary.
|
|
7600
7745
|
|
|
@@ -7630,8 +7775,8 @@ ANIMATION:
|
|
|
7630
7775
|
animate — Loop animation: {itemId, animationType, animationOptions}
|
|
7631
7776
|
animationType: ${SIMPLE_ANIMATION_TYPES_LIST}
|
|
7632
7777
|
keyframe_animate — Timed animation: {itemId, keyframes: [{time, properties, easing}], duration, loop}
|
|
7633
|
-
Animatable properties: opacity, scale, scaleX, scaleY, x, y, rotation, fillColor, strokeColor, fontSize
|
|
7634
|
-
Easing
|
|
7778
|
+
Animatable properties: opacity, scale, scaleX, scaleY, x, y, rotation, fillColor, strokeColor, strokeWidth, width, height, radius, fontSize, trimStart/trimEnd/trimOffset (path draw-on)
|
|
7779
|
+
Easing (on the keyframe being moved TO): ${KEYFRAME_EASINGS_LIST}
|
|
7635
7780
|
relation — Behavioral link: {sourceId, targetId, relationType, relationOptions}
|
|
7636
7781
|
relationType: orbits, follows, attached_to, points_at, mirrors, parallax, wave_through, morphs_to, group_morphs_to, moves_along_path (+ 9 more in schema)
|
|
7637
7782
|
|
|
@@ -7681,8 +7826,8 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
7681
7826
|
description: 'Operation type',
|
|
7682
7827
|
},
|
|
7683
7828
|
// Canvas setup
|
|
7684
|
-
width: { type: 'number', description: 'Canvas width for set_canvas_size (100-
|
|
7685
|
-
height: { type: 'number', description: 'Canvas height for set_canvas_size (100-
|
|
7829
|
+
width: { type: 'number', description: 'Canvas width for set_canvas_size (100-8192)' },
|
|
7830
|
+
height: { type: 'number', description: 'Canvas height for set_canvas_size (100-8192)' },
|
|
7686
7831
|
preset: { type: 'string', description: 'Canvas preset for set_canvas_size' },
|
|
7687
7832
|
backgroundColor: { type: 'string', description: 'For set_background: hex color' },
|
|
7688
7833
|
generatorName: {
|
|
@@ -7834,7 +7979,10 @@ Held exports last until released, or until a LATER export needs the space — an
|
|
|
7834
7979
|
|
|
7835
7980
|
WHAT quality ACTUALLY SETS, because it bundles three things and only one of them is "quality": draft = 15fps / 72dpi / compression 0.6, standard = 30fps / 150dpi / 0.85, high = 60fps / 300dpi / 0.95. So asking for high DOUBLES the frame count and the render time against standard. Override either half directly - fps for frame rate, scale for resolution - and leave quality to mean compression.
|
|
7836
7981
|
|
|
7837
|
-
DOES THIS SURVIVE EXPORT? It is a PATH fact, not a per-feature one, so the rule is short and does not go stale as capabilities land: IF IT TICKS INSIDE THE ENGINE'S UPDATE LOOP, IT EXPORTS. Loop
|
|
7982
|
+
DOES THIS SURVIVE EXPORT? It is a PATH fact, not a per-feature one, so the rule is short and does not go stale as capabilities land: IF IT TICKS INSIDE THE ENGINE'S UPDATE LOOP, IT EXPORTS. Loop presets (pinepaper_animate), relations, keyframes, generators and camera moves all do - measured frame-by-frame, not assumed. What does NOT survive is anything driven by the wall clock outside that loop, or anything on the realtime recorder path.
|
|
7983
|
+
ONE MEASURED EXCEPTION TO CHECK: keyframe_animate's loop:true has been measured playing ONCE in an export and then holding the last keyframe. Before exporting past one cycle, sample a frame in the second cycle; if it holds, write the keyframes out for each cycle, or use a loop preset.
|
|
7984
|
+
|
|
7985
|
+
SEAMLESS LOOPS: there is no loop switch on export. Key the animation to the SAME state at t = 0 and at t = duration, and export exactly that duration — the export renders t = 0 up to one frame before duration, so no frame is doubled at the wrap. A GIF's play count is the loop parameter.
|
|
7838
7986
|
|
|
7839
7987
|
If exported frames look frozen, check the SAMPLING before believing it: a loop animation at animationSpeed 1 has a one-second period, so two frames a whole second apart are identical by design. Compare frames that are not a whole number of periods apart.
|
|
7840
7988
|
|
|
@@ -7848,6 +7996,8 @@ PLATFORMS & OPTIMAL FORMATS:
|
|
|
7848
7996
|
| twitter | 1200x675 | PNG | GIF |
|
|
7849
7997
|
| web | flexible | SVG | SVG |
|
|
7850
7998
|
|
|
7999
|
+
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.
|
|
8000
|
+
|
|
7851
8001
|
FORMATS: svg, png, gif, mp4, webm, pdf.
|
|
7852
8002
|
QUALITY: draft, standard, high.
|
|
7853
8003
|
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).
|
|
@@ -7862,19 +8012,21 @@ EXAMPLES:
|
|
|
7862
8012
|
- {platform: "youtube", format: "mp4", framing: "camera"} — render only what the camera frames during the walkthrough
|
|
7863
8013
|
- {platform: "youtube", format: "mp4", duration: 30, estimateOnly: true} — how big would that be, and what would it lose? (renders nothing)
|
|
7864
8014
|
|
|
7865
|
-
Every export answers the second question too:
|
|
8015
|
+
Every export answers the second question too: the result carries "fidelity", which says whether the check could run at all (available), what this scene loses to the format you chose (warnings), and what that verdict was derived from (checked). An empty warnings list with a note means this SCENE loses nothing to this format — not that the format is lossless.
|
|
8016
|
+
|
|
8017
|
+
VERIFY MOTION BEFORE YOU RENDER. An export takes seconds to minutes and shows you the result only at the end, by which time a frozen animation has cost you the whole render. pinepaper_capture_frames samples the timeline and reports allIdentical plus per-frame hashes, so a scene that is not moving is one cheap call away — it is the fastest way to catch a frozen chart, an animation whose tail never fires, or a curve that was never drawn. Run it before a long export, not after.`,
|
|
7866
8018
|
inputSchema: {
|
|
7867
8019
|
type: 'object',
|
|
7868
8020
|
properties: {
|
|
7869
8021
|
platform: {
|
|
7870
8022
|
type: 'string',
|
|
7871
|
-
enum: ['auto', 'instagram', 'instagram-story', 'tiktok', 'youtube', 'youtube-thumbnail', 'twitter', 'linkedin', 'web', 'print-a4', 'print-letter'],
|
|
8023
|
+
enum: ['auto', 'instagram', 'instagram-story', 'tiktok', 'youtube', 'youtube-thumbnail', 'twitter', 'linkedin', 'web', 'print-a4', 'print-letter', 'print-a4-landscape', 'print-letter-landscape'],
|
|
7872
8024
|
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 REPLACES your dimensions. Every other value forces that platform's frame regardless of the canvas.",
|
|
7873
8025
|
},
|
|
7874
8026
|
format: {
|
|
7875
8027
|
type: 'string',
|
|
7876
|
-
enum: ['svg', 'png', 'gif', 'mp4', 'webm', 'pdf', 'wav'],
|
|
7877
|
-
description: "Override format (auto-detected if not specified). 'wav' exports the SOUNDTRACK ON ITS OWN, with no frames rendered — no platform preset resolves to it, so it must be asked for by name, and platform dimensions, framing and quality do not apply.",
|
|
8028
|
+
enum: ['svg', 'png', 'gif', 'apng', 'mp4', 'webm', 'pdf', 'jpg', 'webp', 'srt', 'vtt', 'wav', 'scc', 'html5-ad', 'playable'],
|
|
8029
|
+
description: "Override format (auto-detected if not specified). 'scc' is broadcast CEA-608 captions (CC1, pop-on, 29.97 drop-frame; two rows of 32 characters, Latin set) from the same timed text as srt / vtt — the result names captions cut, characters dropped and captions that had to go on late. 'html5-ad' is a display-network HTML5 ad (Google Ads, DV360 / CM360, most DSPs): a zip with index.html carrying the ad.size meta and a clickTag click target (the whole ad, or ad.ctaItemId's box), plus a backup PNG saved beside it; the result checks the zip against ad.maxBytes (default 150000) and lists any external requests. 'playable' is an MRAID playable: one HTML file loading mraid.js, whose CTA (ad.ctaItemId, required) opens ad.clickUrl (required) through mraid.open. Both are built from the standalone widget page at the canvas size. 'apng' is animation with FULL alpha (stepped PNG frames, saved as .png; transparent unless transparent: false) — the answer for an animated overlay or sticker, since webm here cannot carry alpha and gif's transparency is 1-bit. 'jpg' / 'webp' are stills for a byte budget (ad specs): the png render re-encoded at the quality tier's compression — draft 0.6, standard 0.85, high 0.95 — so lower quality means a smaller file; 'srt' / 'vtt' write a caption file from every text item that has a lifetime (bornAt / ttl) — one cue each, in start order; duration closes a caption with no ttl. jpg has no transparency: it is flattened onto the scene's background colour, or white when none is set. 'wav' exports the SOUNDTRACK ON ITS OWN, with no frames rendered — no platform preset resolves to it, so it must be asked for by name, and platform dimensions, framing and quality do not apply.",
|
|
7878
8030
|
},
|
|
7879
8031
|
sampleRate: {
|
|
7880
8032
|
type: 'number',
|
|
@@ -7885,22 +8037,63 @@ Every export answers the second question too: where this scene loses something t
|
|
|
7885
8037
|
enum: [16, 32],
|
|
7886
8038
|
description: 'wav only: 16 (default) or 32-bit float. Rejected for any other format.',
|
|
7887
8039
|
},
|
|
8040
|
+
ad: {
|
|
8041
|
+
type: 'object',
|
|
8042
|
+
description: 'html5-ad / playable options.',
|
|
8043
|
+
properties: {
|
|
8044
|
+
clickUrl: { type: 'string', description: 'html5-ad: the clickTag value for testing (networks replace it at serve time). playable: the store / landing URL the CTA opens (required).' },
|
|
8045
|
+
ctaItemId: { type: 'string', description: 'The item whose bounds are the click target. Optional for html5-ad (default: whole ad); required for playable. Static: where the item is at export time.' },
|
|
8046
|
+
maxBytes: { type: 'integer', minimum: 1, description: 'Upload size budget (zip for html5-ad, HTML for playable). html5-ad default 150000 (Google Ads display); set your network figure.' },
|
|
8047
|
+
},
|
|
8048
|
+
},
|
|
8049
|
+
transparent: { type: 'boolean', description: 'apng: keep the alpha channel (the default; false fills the background colour). webm: true writes a VP9 alpha channel where the studio supports it; result.video.alpha confirms it, and fidelity says when it did not. Not for mp4 / gif.' },
|
|
8050
|
+
alphaQuantizer: { type: 'integer', minimum: 0, maximum: 63, description: 'webm with transparent: alpha-stream quality, 0 = lossless … 63.' },
|
|
8051
|
+
loop: { anyOf: [{ type: 'boolean' }, { type: 'integer', minimum: 0, maximum: 1000 }], description: 'gif / apng: true = loop forever, false / 0 / 1 = play once, n = play n times (email clients often want a few plays, not forever). Refused on video formats — see SEAMLESS LOOPS.' },
|
|
8052
|
+
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.' },
|
|
8053
|
+
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.' },
|
|
8054
|
+
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.' },
|
|
8055
|
+
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.' },
|
|
8056
|
+
bitrateMode: { type: 'string', enum: ['constant', 'variable'], description: "mp4 / webm: 'constant' (the default with broadcast) or 'variable'." },
|
|
8057
|
+
maxBytes: { type: 'integer', minimum: 1, description: 'gif only: size budget in bytes (email: 1000000). Over it, the GIF is re-encoded at a smaller frame size, at most twice; result.budget reports each attempt.' },
|
|
8058
|
+
time: { type: 'number', minimum: 0, description: 'Stills only (png / jpg / webp / svg / pdf): render at this time in seconds; the playhead is put back afterwards. Without it a still is whatever frame the playhead is on, so repeated runs can differ.' },
|
|
8059
|
+
pdf: {
|
|
8060
|
+
type: 'object',
|
|
8061
|
+
description: "pdf only: print options. paperFormat 'a4' | 'a3' | 'letter' | … | 'custom' (canvas size, default); orientation; bleed in mm (> 0 includes the bleed area); trimMarks adds crop marks; dpi 72–600. Bleed and trim marks take effect with paperFormat 'custom' (the page is the canvas plus bleed) — on a named size the page stays that size and they are dropped, which the result's warnings say. The canvas's physical size comes from its preset's DPI: start the job with canvasPreset 'print-a4' / 'print-letter' / 'print-a4-landscape' / 'print-letter-landscape' (300 dpi) for print; a hand-sized canvas is read at 96 dpi.",
|
|
8062
|
+
properties: {
|
|
8063
|
+
paperFormat: { type: 'string' },
|
|
8064
|
+
orientation: { type: 'string', enum: ['portrait', 'landscape'] },
|
|
8065
|
+
bleed: { type: 'number', minimum: 0, maximum: 20 },
|
|
8066
|
+
trimMarks: { type: 'boolean' },
|
|
8067
|
+
searchableText: { type: 'boolean', description: 'Default true where the studio supports it: an invisible text layer over the artwork, so the PDF is selectable, searchable and screen-readable. Lines no font in the PDF can carry (older studios have only a built-in Latin font) are drawn but left out, and fidelity says how many and which fonts could not be embedded. Multi-page (pages) exports get it per page on studios that support it. false omits it. result.pdf reports linesWritten / linesSkipped.' },
|
|
8068
|
+
dpi: { type: 'integer', minimum: 72, maximum: 600 },
|
|
8069
|
+
pages: { anyOf: [{ type: 'string', enum: ['scenes'] }, { type: 'array', items: { anyOf: [{ type: 'string' }, { type: 'object', properties: { sceneId: { type: 'string' }, width: { type: 'number' }, height: { type: 'number' } }, required: ['sceneId', 'width', 'height'] }] }, minItems: 1 }], description: "Multi-page PDF: 'scenes' = one page per saved scene (the scenes tool's save action) in timeline order, or an ordered list of scene ids. Saved scenes do NOT record their canvas size, so every page uses the current canvas size unless you give it: {sceneId, width, height} (pixels) per page. Pages are embedded as JPEG at the quality tier's compression." },
|
|
8070
|
+
},
|
|
8071
|
+
},
|
|
8072
|
+
region: {
|
|
8073
|
+
type: 'object',
|
|
8074
|
+
description: 'png / jpg / webp only: export just this part of the canvas (canvas coordinates, x/y = TOP-LEFT) — carousel slices, crops. Output is the region\'s own size unless outputWidth/outputHeight say otherwise; a different aspect is covered, not stretched. SMALL ASSETS (emotes, badges, favicons): design at 2-4x on a canvas of at least 100 px, then export the whole board as a region with the small outputWidth/outputHeight — e.g. a 112x112 canvas, region {x:0, y:0, width:112, height:112, outputWidth:28, outputHeight:28}.',
|
|
8075
|
+
properties: {
|
|
8076
|
+
x: { type: 'number' }, y: { type: 'number' },
|
|
8077
|
+
width: { type: 'number', exclusiveMinimum: 0 }, height: { type: 'number', exclusiveMinimum: 0 },
|
|
8078
|
+
outputWidth: { type: 'integer', minimum: 1, maximum: 8192 }, outputHeight: { type: 'integer', minimum: 1, maximum: 8192 },
|
|
8079
|
+
excludeForeign: { type: 'boolean', description: 'Default true: an item reaching into the region but centred outside it (a neighbouring card overflow) is left out of this image; result.excludedItems names them. false renders everything that touches the region.' },
|
|
8080
|
+
},
|
|
8081
|
+
required: ['x', 'y', 'width', 'height'],
|
|
8082
|
+
},
|
|
7888
8083
|
quality: {
|
|
7889
8084
|
type: 'string',
|
|
7890
8085
|
enum: ['draft', 'standard', 'high'],
|
|
7891
8086
|
description: 'Compression level (default: standard). It ALSO carries a default frame rate — draft 15, standard 30, high 60 — so "high" doubles the frame count and the render time against "standard" unless you set fps yourself. Set fps and scale to control those halves directly and leave quality meaning compression.',
|
|
7892
8087
|
},
|
|
7893
8088
|
fps: {
|
|
7894
|
-
type: '
|
|
7895
|
-
|
|
7896
|
-
maximum: 120,
|
|
7897
|
-
description: 'Video only: frames per second, overriding whatever `quality` implies. File size is bitrate x duration and does not move with fps, but the picture gets fewer bits per frame at a higher one.',
|
|
8089
|
+
anyOf: [{ type: 'number', minimum: 1, maximum: 120 }, { type: 'string', pattern: '^[0-9]+/[0-9]+$' }],
|
|
8090
|
+
description: 'Video only: frames per second, overriding whatever `quality` implies. Integers, NTSC rates (23.976, 29.97, 59.94 — snapped to the exact 24000/1001, 30000/1001, 60000/1001, which CTV / OTT specs require) or a rational string "30000/1001". At an NTSC rate a duration of N seconds is round(N x fps) frames, so 6 s at 29.97 is 180 frames = 6.006 s — the result says when that overruns the duration you asked for. File size is bitrate x duration and does not move with fps.',
|
|
7898
8091
|
},
|
|
7899
8092
|
scale: {
|
|
7900
8093
|
type: 'number',
|
|
7901
8094
|
minimum: 0.1,
|
|
7902
8095
|
maximum: 1,
|
|
7903
|
-
description: 'Video only: render at this fraction of the platform preset dimensions (0.1-1). The engine derives its encode target from RESOLUTION, so this is the size control
|
|
8096
|
+
description: 'Video only: render at this fraction of the platform preset dimensions (0.1-1). The engine derives its encode target from RESOLUTION unless bitrate is given, so this is the size control; halving the frame roughly quarters the pixels and the file. It is also the preview knob: scale 0.5 with quality "draft" is the fast look-check before committing to a full render. Rounded to even dimensions, which H.264 requires.',
|
|
7904
8097
|
},
|
|
7905
8098
|
framing: {
|
|
7906
8099
|
type: 'string',
|
|
@@ -8472,6 +8665,7 @@ DATA FORMAT: Array of objects, e.g. [{category: "A", value: 10}, {category: "B",
|
|
|
8472
8665
|
|
|
8473
8666
|
OPTIONS (vary by chart type):
|
|
8474
8667
|
- Common: title, width, height, x, y, colors, animation, axisColor, labelColor, labelFontSize
|
|
8668
|
+
x / y are the chart's CENTRE (measured: x 1000, y 300 at 800x600 reads back as bounds [600, -25, 800, 625]) — pass the centre of the rect you want it in, not its top-left.
|
|
8475
8669
|
- Bar: barWidth, barGap, xLabel, yLabel
|
|
8476
8670
|
orient: 'horizontal' flips bars to grow rightward from a left Y-axis — best for long category names
|
|
8477
8671
|
labelArrangement: 'auto'|'horizontal'|'slanted'|'vertical'|'truncate'|'wrap'
|