@pinepaper.studio/mcp-server 1.6.12 → 1.6.14
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 +341 -4
- package/README.pt-BR.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/agent/export-manager.d.ts.map +1 -1
- package/dist/agent/export-manager.js +9 -7
- package/dist/agent/export-manager.js.map +1 -1
- package/dist/assets/repositories/iconify.d.ts +0 -5
- package/dist/assets/repositories/iconify.d.ts.map +1 -1
- package/dist/assets/repositories/iconify.js +17 -5
- package/dist/assets/repositories/iconify.js.map +1 -1
- package/dist/browser/puppeteer-controller.d.ts +16 -0
- package/dist/browser/puppeteer-controller.d.ts.map +1 -1
- package/dist/browser/puppeteer-controller.js +32 -2
- package/dist/browser/puppeteer-controller.js.map +1 -1
- package/dist/build-info.json +5 -0
- package/dist/design/design-systems.d.ts +27 -2
- package/dist/design/design-systems.d.ts.map +1 -1
- package/dist/design/design-systems.js +20 -0
- 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 +299 -75
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-methods.d.ts.map +1 -1
- package/dist/tools/engine-methods.js +28 -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 +18 -12
- 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 +24 -3
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +645 -27
- 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 +80 -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 +12 -1
- 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 +2871 -316
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +1595 -271
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +269 -31
- 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/manifest.json +9 -1
- package/package.json +6 -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',
|
|
@@ -338,10 +342,19 @@ USE WHEN:
|
|
|
338
342
|
|
|
339
343
|
IMPORTANT NOTES:
|
|
340
344
|
- This reloads the entire page, which resets everything
|
|
341
|
-
- The welcome template will NOT appear after refresh (only shows for first-time visitors)
|
|
342
|
-
- After refresh, the canvas will be completely empty
|
|
343
345
|
- All unsaved work will be lost
|
|
344
|
-
- You may need to wait a moment after refresh before executing other commands
|
|
346
|
+
- You may need to wait a moment after refresh before executing other commands
|
|
347
|
+
|
|
348
|
+
WHAT YOU ACTUALLY GET, which is not always an empty canvas:
|
|
349
|
+
- The template picker and a "Restore your previous work?" dialog can both
|
|
350
|
+
appear after a reload — observed in agent mode. A screenshot taken straight
|
|
351
|
+
afterwards shows those, not your scene, so do not read one as evidence that
|
|
352
|
+
the canvas is broken or that your items failed to draw.
|
|
353
|
+
- Dismiss or clear whatever appears before judging the result, and prefer
|
|
354
|
+
pinepaper_agent_reset when you want a clean canvas rather than a clean PAGE:
|
|
355
|
+
it skips the reload and the dialogs entirely.
|
|
356
|
+
- This description used to promise the welcome template would not appear and
|
|
357
|
+
that the canvas would be empty. Neither is reliable, so neither is claimed.`,
|
|
345
358
|
inputSchema: {
|
|
346
359
|
type: 'object',
|
|
347
360
|
properties: {},
|
|
@@ -391,6 +404,10 @@ its own size. anchor: 'top-left' (also top-right, bottom-left, bottom-right,
|
|
|
391
404
|
center; origin is an alias for the parameter) fixes that without you computing
|
|
392
405
|
bounds you cannot know until the item exists. It is applied after size and
|
|
393
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).
|
|
394
411
|
|
|
395
412
|
EVERY type below also takes a label, which wraps the item in a labelled group —
|
|
396
413
|
you do not need a second create call and a hand-computed position to caption a
|
|
@@ -404,7 +421,12 @@ Labelling a text item gives you TWO text items in the group — the body and the
|
|
|
404
421
|
label. That is what you want for a caption; it is a surprise if you expected
|
|
405
422
|
the label to replace the content.
|
|
406
423
|
|
|
407
|
-
- 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.
|
|
408
430
|
- circle: Circular shape (properties: radius, color, strokeColor, strokeWidth)
|
|
409
431
|
- star: Star shape (properties: radius1, radius2, points, color)
|
|
410
432
|
radius1 is the OUTER radius and is what sizes the star; radius or
|
|
@@ -441,6 +463,7 @@ the label to replace the content.
|
|
|
441
463
|
positioned it. Offset the points yourself, or use sides + radius + position.
|
|
442
464
|
- ellipse: Oval shape (properties: width, height, color) — defaults to 100x60
|
|
443
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.
|
|
444
467
|
- line: Line between two points (properties: from, to, strokeColor, strokeWidth)
|
|
445
468
|
- arc: Curved arc through three points (properties: from, through, to, strokeColor)
|
|
446
469
|
- pentagon: Regular 5-sided polygon (properties: radius, color)
|
|
@@ -487,6 +510,7 @@ Use itemType: "path" with either:
|
|
|
487
510
|
|
|
488
511
|
ADVANCED STYLING (NEW):
|
|
489
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.
|
|
490
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]}
|
|
491
515
|
- Shadows: shadowColor, shadowBlur (pixels), shadowOffset: [x, y]
|
|
492
516
|
- Blend modes: blendMode: "multiply"|"screen"|"overlay"|"darken"|"lighten"|etc.
|
|
@@ -565,6 +589,11 @@ LIFETIME — on ANY item, and the only way to build a piece that CUTS between sh
|
|
|
565
589
|
- ttl: seconds it lives for. Omit for "until the end".
|
|
566
590
|
Without these every item is on screen for the whole render, which is what makes
|
|
567
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.
|
|
568
597
|
|
|
569
598
|
SHADER (itemType 'shader') — a lit surface, drawn per pixel:
|
|
570
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'
|
|
@@ -587,6 +616,11 @@ thousands of authored items.
|
|
|
587
616
|
- Example — a rolling swell: xExpr "(i % 120) * 8", yExpr "400 + sin((i % 120) * 0.4 - t * 2) * 12", count 3600`,
|
|
588
617
|
additionalProperties: true,
|
|
589
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
|
+
},
|
|
590
624
|
data: {
|
|
591
625
|
type: 'object',
|
|
592
626
|
description: 'Item data flags for selection and behavior control',
|
|
@@ -659,6 +693,7 @@ MODIFIABLE PROPERTIES:
|
|
|
659
693
|
- content: Text content
|
|
660
694
|
- opacity: Transparency (0-1)
|
|
661
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.
|
|
662
697
|
- scale: Size multiplier
|
|
663
698
|
- shadowColor: Shadow color
|
|
664
699
|
- shadowBlur: Shadow blur radius
|
|
@@ -957,6 +992,8 @@ USE WHEN:
|
|
|
957
992
|
- Adding logos or vector graphics
|
|
958
993
|
- User mentions SVG files or vector graphics
|
|
959
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
|
+
|
|
960
997
|
⚠️ RECOMMENDED FOR COMPLEX CHARACTERS:
|
|
961
998
|
Instead of drawing "a witch" or "a cat" with many path operations, use SVG import:
|
|
962
999
|
1. Describe what you need and use a simple placeholder SVG
|
|
@@ -1120,6 +1157,11 @@ EXAMPLES:
|
|
|
1120
1157
|
enum: ['circle', 'rounded', 'hexagon', 'star'],
|
|
1121
1158
|
description: 'Optional mask shape to apply to the image',
|
|
1122
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
|
+
},
|
|
1123
1165
|
},
|
|
1124
1166
|
required: ['url'],
|
|
1125
1167
|
},
|
|
@@ -1530,7 +1572,7 @@ warp (optional): chained parametric warp(s) — displace each sampled point by {
|
|
|
1530
1572
|
|
|
1531
1573
|
RETURNS: { success, kind, itemId } — itemId is the created path. Bad expressions / empty ranges return { success:false, error }.
|
|
1532
1574
|
|
|
1533
|
-
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.
|
|
1534
1576
|
|
|
1535
1577
|
EXAMPLES:
|
|
1536
1578
|
- Sine wave: { kind: "function", expr: "sin(x/40) * 60", min: -300, max: 300, style: { strokeColor: "#4f46e5" } }
|
|
@@ -1555,7 +1597,7 @@ EXAMPLES:
|
|
|
1555
1597
|
y: { type: 'number', description: 'Canvas origin y (default: canvas center)' },
|
|
1556
1598
|
flipY: { type: 'boolean', description: 'Math y-up → screen y-down (default true)' },
|
|
1557
1599
|
style: { type: 'object', description: 'Path style forwarded to create (strokeColor, strokeWidth, fillColor, …)' },
|
|
1558
|
-
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." },
|
|
1559
1601
|
warp: {
|
|
1560
1602
|
anyOf: [{ type: 'object' }, { type: 'array', items: { type: 'object' } }],
|
|
1561
1603
|
description: 'Chained parametric warp(s): { dx, dy } expressions of x, y, t — one object or an array',
|
|
@@ -1687,6 +1729,75 @@ EXAMPLES:
|
|
|
1687
1729
|
required: ['action'],
|
|
1688
1730
|
},
|
|
1689
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
|
+
},
|
|
1690
1801
|
{
|
|
1691
1802
|
name: 'pinepaper_camera_director',
|
|
1692
1803
|
annotations: {
|
|
@@ -1933,7 +2044,7 @@ EXAMPLE: { times: [0, 0.5, 1, 1.5, 2], seed: 42 }`,
|
|
|
1933
2044
|
|
|
1934
2045
|
ACTIONS:
|
|
1935
2046
|
- upload_video: { url (required), position?, scale?, timeOffset? (timeline start, s), clipInPoint?, clipOutPoint? (trim, s) } → { id, duration, width, height, name }
|
|
1936
|
-
- 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.
|
|
1937
2048
|
- list: → all media [{ id, kind, duration, … }]
|
|
1938
2049
|
- remove: { id } → removed boolean
|
|
1939
2050
|
- set_playback_rate: { id, rate (0.25–4) } → applies to video or audio
|
|
@@ -1971,7 +2082,7 @@ EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, time
|
|
|
1971
2082
|
strength: { type: 'number', description: 'apply_track_matte: 0–1.' },
|
|
1972
2083
|
hideMatte: { type: 'boolean', description: 'apply_track_matte: hide the matte item after applying.' },
|
|
1973
2084
|
live: { type: 'boolean', description: 'apply_track_matte: keep re-cutting as the matte moves/animates — the kinetic mask reveal. Omitted = bake once, destructively.' },
|
|
1974
|
-
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." },
|
|
1975
2086
|
id: { type: 'string', description: 'Media id — required for remove / set_playback_rate / set_clip.' },
|
|
1976
2087
|
rate: { type: 'number', description: 'Playback rate 0.25–4 — set_playback_rate.' },
|
|
1977
2088
|
inPoint: { type: 'number', description: 'Clip in-point (media-time s) — set_clip.' },
|
|
@@ -1982,7 +2093,7 @@ EXAMPLE: { action: 'upload_video', url: 'https://…/clip.mp4', scale: 0.5, time
|
|
|
1982
2093
|
clipInPoint: { type: 'number', description: 'Trim in-point (s) — upload_video.' },
|
|
1983
2094
|
clipOutPoint: { type: 'number', description: 'Trim out-point (s) — upload_video.' },
|
|
1984
2095
|
volume: { type: 'number', description: 'Volume 0–1 (default 1) — upload_audio.' },
|
|
1985
|
-
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.' },
|
|
1986
2097
|
muted: { type: 'boolean', description: 'Start muted — upload_audio.' },
|
|
1987
2098
|
},
|
|
1988
2099
|
required: ['action'],
|
|
@@ -2004,11 +2115,24 @@ ACTIONS:
|
|
|
2004
2115
|
- 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.
|
|
2005
2116
|
- list_styles: {} — styles + palettes + axes, for pickers. Call this first; styleKey strings come from here.
|
|
2006
2117
|
|
|
2118
|
+
OVER PHOTOGRAPHS, PASS A PALETTE — NEVER THE DEFAULT. Measured on a real
|
|
2119
|
+
render of captions over photographs: highlighter, sticker, caption and badge
|
|
2120
|
+
read correctly at the default palette. neon needs one on a dark photo. chrome,
|
|
2121
|
+
cutout and glitch are off-brief at the default palette over any photograph —
|
|
2122
|
+
they are designed against flat backgrounds and the default palette assumes one.
|
|
2123
|
+
A style that is hard to read still reports success, so this is a judgement the
|
|
2124
|
+
tool cannot make for you.
|
|
2125
|
+
|
|
2126
|
+
MEASURE THE WIDTH BEFORE COMMITTING TO A SIZE. glitch at 80px in a monospace
|
|
2127
|
+
face overflows a 1920 canvas at 36 characters. Layered styles draw offset
|
|
2128
|
+
copies, so the group is WIDER than the text — use 'wrap' first, or shorten the
|
|
2129
|
+
line, rather than discovering it in an export.
|
|
2130
|
+
|
|
2007
2131
|
EXAMPLE: { action: 'apply_style', itemId: 'title_1', styleKey: 'arcade', fontFamily: 'suggested' }`,
|
|
2008
2132
|
inputSchema: {
|
|
2009
2133
|
type: 'object',
|
|
2010
2134
|
properties: {
|
|
2011
|
-
action: { type: 'string', enum: ['apply_style', 'set_font_axes', 'list_styles', 'cursive', 'wrap', 'unwrap', 'to_collage'], description: "Text style operation. '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." },
|
|
2135
|
+
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." },
|
|
2012
2136
|
text: { type: 'string', description: 'cursive: the words to write. to_collage: the text to build from, if it differs from the item.' },
|
|
2013
2137
|
maxWidth: { type: 'number', description: 'wrap: the width to break at, in canvas units. unwrap restores the original, so this is not destructive.' },
|
|
2014
2138
|
cursiveOptions: { type: 'object', description: 'cursive: { x?, y?, scale?, strokeColor?, strokeWidth? }.' },
|
|
@@ -2278,9 +2402,9 @@ RECIPE — assemble-from-pieces: shatter with keepSource, keyframe each tile fro
|
|
|
2278
2402
|
|
|
2279
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.
|
|
2280
2404
|
|
|
2281
|
-
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.
|
|
2282
2406
|
|
|
2283
|
-
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? }
|
|
2284
2408
|
Returns: { groupId, parts, roles, rolesWired, warnings }
|
|
2285
2409
|
|
|
2286
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.`,
|
|
@@ -2464,7 +2588,7 @@ Three of these refuse quietly in the engine: exportGLB with no perspective objec
|
|
|
2464
2588
|
type: 'object',
|
|
2465
2589
|
properties: {
|
|
2466
2590
|
action: { type: 'string', enum: ['export_lottie', 'export_dotlottie', 'import_lottie', 'export_glb', 'export_bvh', 'export_png_sequence'], description: 'Which interchange operation.' },
|
|
2467
|
-
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." },
|
|
2468
2592
|
data: { anyOf: [{ type: 'string' }, { type: 'object' }], description: 'import_lottie: the Lottie JSON — object, JSON string, or URL.' },
|
|
2469
2593
|
skeletonId: { type: 'string', description: "export_bvh: which skeleton to write. A BVH is one skeleton's motion." },
|
|
2470
2594
|
},
|
|
@@ -2496,7 +2620,8 @@ render_soundtrack mixes every placed sound to a WAV and writes it to a file. It
|
|
|
2496
2620
|
inputSchema: {
|
|
2497
2621
|
type: 'object',
|
|
2498
2622
|
properties: {
|
|
2499
|
-
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' } },
|
|
2500
2625
|
partials: { type: 'array', items: { type: 'object' }, description: 'define_*: [{ h, amp }] — the harmonic table that IS the timbre. Required for define_instrument.' },
|
|
2501
2626
|
envelope: { type: 'object', description: 'define_*: { attack, decay, sustain, release } — seconds, except sustain which is a level 0-1.' },
|
|
2502
2627
|
gain: { type: 'number', description: 'define_*: output level 0-1 (0.8 instrument, 0.85 percussion, 0.65 sfx).' },
|
|
@@ -2628,7 +2753,7 @@ RECIPE — a character walking through a forest: create {spec:'forest'} → impo
|
|
|
2628
2753
|
camera: { type: 'object', description: "set_camera: { mode: follow|fixed|orbit, target?, radius?, speed?, eye?, lookAt? }." },
|
|
2629
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.' },
|
|
2630
2755
|
pathId: { type: 'string', description: 'extrude_path / lathe_path: the canvas path to turn into geometry. The path itself is unchanged.' },
|
|
2631
|
-
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.' },
|
|
2632
2757
|
meshId: { type: 'string', description: 'remove_mesh: the mesh id.' },
|
|
2633
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.' },
|
|
2634
2759
|
lightId: { type: 'string', description: 'set_light / remove_light: the light id.' },
|
|
@@ -2929,6 +3054,11 @@ PARAMS: { eps? (bounds-match tolerance px, default 4), cap? (max suggestions, de
|
|
|
2929
3054
|
},
|
|
2930
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.
|
|
2931
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
|
+
|
|
2932
3062
|
USE WHEN:
|
|
2933
3063
|
- "moon orbits earth" → relationType: orbits
|
|
2934
3064
|
- "label follows player" → relationType: follows
|
|
@@ -2994,7 +3124,7 @@ RELATION COMPATIBILITY:
|
|
|
2994
3124
|
Text→Text: font blend
|
|
2995
3125
|
Cross-type: particle denoising transition
|
|
2996
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.
|
|
2997
|
-
- 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).
|
|
2998
3128
|
|
|
2999
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:
|
|
3000
3130
|
- is_midpoint_of: source = midpoint(target, params.other). params.other = id of the second endpoint (B).
|
|
@@ -3372,9 +3502,13 @@ ANIMATABLE PROPERTIES:
|
|
|
3372
3502
|
- opacity: 0-1
|
|
3373
3503
|
- fillColor: Color string
|
|
3374
3504
|
- strokeColor: Color string
|
|
3505
|
+
- strokeWidth, width, height, radius, shadowBlur, shadowColor: Numbers / colours
|
|
3375
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.
|
|
3376
3509
|
|
|
3377
|
-
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.
|
|
3378
3512
|
- linear: Constant speed
|
|
3379
3513
|
- easeIn: Slow start
|
|
3380
3514
|
- easeOut: Slow end
|
|
@@ -3410,6 +3544,7 @@ EXAMPLE — Reveal at 2s, skip first second of keyframe data, end at 4s of keyfr
|
|
|
3410
3544
|
easing: {
|
|
3411
3545
|
type: 'string',
|
|
3412
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.',
|
|
3413
3548
|
},
|
|
3414
3549
|
},
|
|
3415
3550
|
required: ['time', 'properties'],
|
|
@@ -3617,7 +3752,9 @@ USE WHEN:
|
|
|
3617
3752
|
- Creating complex reveal sequences
|
|
3618
3753
|
- Building custom transitions
|
|
3619
3754
|
|
|
3620
|
-
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.`,
|
|
3621
3758
|
inputSchema: {
|
|
3622
3759
|
type: 'object',
|
|
3623
3760
|
properties: {
|
|
@@ -3754,7 +3891,6 @@ USE WHEN:
|
|
|
3754
3891
|
- Building presentation zoom sequences
|
|
3755
3892
|
- Making dramatic reveals with camera motion
|
|
3756
3893
|
- Tour animations showing different parts of canvas
|
|
3757
|
-
- 3D perspective effects (pitch/yaw tilt via equirectangular projection)
|
|
3758
3894
|
|
|
3759
3895
|
The camera follows a keyframe sequence. For an orbit-style camera (revolving
|
|
3760
3896
|
around a subject), add 4-8 keyframes around the pivot using pathMode: "arc"
|
|
@@ -3769,8 +3905,7 @@ KEYFRAME PROPERTIES:
|
|
|
3769
3905
|
'item-id' itemRegistry lookup → item bounds center (tracks the item as it moves)
|
|
3770
3906
|
{ item: 'id', offset: [dx, dy] } item center + offset
|
|
3771
3907
|
- center: [x, y] — legacy alias, honored only when focus is absent
|
|
3772
|
-
- pitch: 3D tilt in degrees
|
|
3773
|
-
- 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.
|
|
3774
3909
|
- easing: Timing function (linear, easeIn, easeOut, easeInOut, bounce, elastic)
|
|
3775
3910
|
|
|
3776
3911
|
CURVED INTER-KEYFRAME PATHS (optional; default is linear):
|
|
@@ -3822,8 +3957,8 @@ EXAMPLE (curved arc between waypoints):
|
|
|
3822
3957
|
items: { type: 'number' },
|
|
3823
3958
|
description: 'Legacy view center [x, y] — honored only when focus is absent',
|
|
3824
3959
|
},
|
|
3825
|
-
pitch: { type: 'number', description: '3D tilt in degrees
|
|
3826
|
-
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).' },
|
|
3827
3962
|
easing: {
|
|
3828
3963
|
type: 'string',
|
|
3829
3964
|
enum: ['linear', 'easeIn', 'easeOut', 'easeInOut', 'bounce', 'elastic'],
|
|
@@ -3860,12 +3995,12 @@ EXAMPLE (curved arc between waypoints):
|
|
|
3860
3995
|
},
|
|
3861
3996
|
},
|
|
3862
3997
|
},
|
|
3863
|
-
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.' },
|
|
3864
3999
|
loop: { type: 'boolean', description: 'Loop the animation' },
|
|
3865
4000
|
delay: { type: 'number', description: 'Delay before animation starts' },
|
|
3866
4001
|
fov: { type: 'number', description: 'Field of view in degrees for 3D perspective (default: 60)' },
|
|
3867
4002
|
},
|
|
3868
|
-
required: ['keyframes'
|
|
4003
|
+
required: ['keyframes'],
|
|
3869
4004
|
},
|
|
3870
4005
|
},
|
|
3871
4006
|
{
|
|
@@ -4004,8 +4139,8 @@ SUPPORTED ANIMATIONS: bounce, breathe, fade, glow, jelly, path, pulse, rotate, s
|
|
|
4004
4139
|
},
|
|
4005
4140
|
itemType: {
|
|
4006
4141
|
type: 'string',
|
|
4007
|
-
enum: [
|
|
4008
|
-
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.',
|
|
4009
4144
|
},
|
|
4010
4145
|
position: {
|
|
4011
4146
|
type: 'object',
|
|
@@ -4293,7 +4428,7 @@ EXAMPLE:
|
|
|
4293
4428
|
},
|
|
4294
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.
|
|
4295
4430
|
|
|
4296
|
-
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.
|
|
4297
4432
|
|
|
4298
4433
|
ACTIONS:
|
|
4299
4434
|
- apply: write the delays onto real items, in the order you list them (row-major for a grid).
|
|
@@ -4760,7 +4895,7 @@ Both actions return a WCAG contrast audit, so you can see failing pairs before s
|
|
|
4760
4895
|
ACTIONS:
|
|
4761
4896
|
- define: Turn items into a component master. Params: itemIds[], name
|
|
4762
4897
|
- list: All defined components
|
|
4763
|
-
- 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.
|
|
4764
4899
|
- set_override: Change ONE part of ONE instance without detaching it. Params: instanceId, componentKey, prop, value
|
|
4765
4900
|
- sync: Push the master's current state to every instance (overrides are preserved). Params: componentId
|
|
4766
4901
|
- update_from_instance: Promote an instance's edits back into the master. Params: instanceId
|
|
@@ -4788,7 +4923,7 @@ Overrides are the point: an instance can differ from its master and still receiv
|
|
|
4788
4923
|
anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }, { type: 'object' }, { type: 'array' }],
|
|
4789
4924
|
description: 'Override value',
|
|
4790
4925
|
},
|
|
4791
|
-
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.' },
|
|
4792
4927
|
},
|
|
4793
4928
|
required: ['action'],
|
|
4794
4929
|
},
|
|
@@ -4933,7 +5068,7 @@ node survives none of them.`,
|
|
|
4933
5068
|
type: 'object',
|
|
4934
5069
|
properties: {
|
|
4935
5070
|
action: { type: 'string', enum: ['analyze', 'animate_to_beat'] },
|
|
4936
|
-
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.' },
|
|
4937
5072
|
itemId: { type: 'string' },
|
|
4938
5073
|
beats: { type: 'array', items: { type: 'number' }, description: 'Beat times (s) you already have' },
|
|
4939
5074
|
grid: { type: 'boolean', description: 'Even pulse from the tempo instead of raw onsets' },
|
|
@@ -5023,26 +5158,28 @@ ACTIONS:
|
|
|
5023
5158
|
|
|
5024
5159
|
Available filters (GPU raster set):
|
|
5025
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)
|
|
5026
|
-
- 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)
|
|
5027
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)
|
|
5028
5163
|
- Keying: lumaKey (threshold, softness, invert)
|
|
5029
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)
|
|
5030
5165
|
|
|
5031
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.
|
|
5032
5169
|
(Scene-wide filters — sharpen/emboss/noise/vintage etc. — are a different surface: use pinepaper_add_filter.)`,
|
|
5033
5170
|
inputSchema: {
|
|
5034
5171
|
type: 'object',
|
|
5035
5172
|
properties: {
|
|
5036
5173
|
action: {
|
|
5037
5174
|
type: 'string',
|
|
5038
|
-
enum: ['apply', 'chain'],
|
|
5175
|
+
enum: ['apply', 'chain', 'analyze_palette', 'recolor_palette'],
|
|
5039
5176
|
description: 'Filter action',
|
|
5040
5177
|
},
|
|
5041
5178
|
itemId: { type: 'string', description: 'Target raster item ID' },
|
|
5042
5179
|
filterName: { type: 'string', description: 'Filter name (for apply)' },
|
|
5043
5180
|
params: {
|
|
5044
5181
|
type: 'object',
|
|
5045
|
-
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.",
|
|
5046
5183
|
},
|
|
5047
5184
|
filters: {
|
|
5048
5185
|
type: 'array',
|
|
@@ -5054,8 +5191,18 @@ RECIPE — grunge poster: chain [grain, scanlines, vignette]. Glass header: refr
|
|
|
5054
5191
|
},
|
|
5055
5192
|
required: ['name'],
|
|
5056
5193
|
},
|
|
5057
|
-
description: 'Filter chain (for chain action)',
|
|
5194
|
+
description: 'Filter chain (for chain action). Order matters — see CHAIN ORDER above.',
|
|
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.",
|
|
5058
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).' },
|
|
5059
5206
|
},
|
|
5060
5207
|
required: ['action', 'itemId'],
|
|
5061
5208
|
},
|
|
@@ -5069,20 +5216,28 @@ RECIPE — grunge poster: chain [grain, scanlines, vignette]. Glass header: refr
|
|
|
5069
5216
|
idempotentHint: false,
|
|
5070
5217
|
openWorldHint: false,
|
|
5071
5218
|
},
|
|
5072
|
-
description: `
|
|
5219
|
+
description: `Cut a region out of an image along a polygon — the subject cutout behind 2.5D parallax, stickers and collage.
|
|
5073
5220
|
|
|
5074
5221
|
ACTIONS:
|
|
5075
|
-
-
|
|
5076
|
-
- 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.`,
|
|
5077
5226
|
inputSchema: {
|
|
5078
5227
|
type: 'object',
|
|
5079
5228
|
properties: {
|
|
5080
5229
|
action: {
|
|
5081
5230
|
type: 'string',
|
|
5082
|
-
enum: ['activate', 'apply'],
|
|
5083
|
-
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'] }] },
|
|
5084
5239
|
},
|
|
5085
|
-
|
|
5240
|
+
keepOriginal: { type: 'boolean', description: 'cut: keep the source image (default true). false = the source is consumed.' },
|
|
5086
5241
|
},
|
|
5087
5242
|
required: ['action'],
|
|
5088
5243
|
},
|
|
@@ -5514,6 +5669,9 @@ Params: title (page title), download (bool)`,
|
|
|
5514
5669
|
type: 'object',
|
|
5515
5670
|
properties: {
|
|
5516
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.' },
|
|
5517
5675
|
download: { type: 'boolean', description: 'Trigger browser download (default: false)' },
|
|
5518
5676
|
},
|
|
5519
5677
|
},
|
|
@@ -6419,6 +6577,7 @@ ACTIONS:
|
|
|
6419
6577
|
|
|
6420
6578
|
ACTIONS:
|
|
6421
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)
|
|
6422
6581
|
- rotate_to — { lon, lat, duration? } versor-slerp the globe to face a coordinate
|
|
6423
6582
|
- spin — { speed?, axis?: "longitude"|"latitude", duration? } continuous auto-rotation
|
|
6424
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)
|
|
@@ -6428,7 +6587,7 @@ ACTIONS:
|
|
|
6428
6587
|
inputSchema: {
|
|
6429
6588
|
type: 'object',
|
|
6430
6589
|
properties: {
|
|
6431
|
-
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' },
|
|
6432
6591
|
rotation: { type: 'array', items: { type: 'number' }, description: 'Initial rotation [lambda, phi, gamma] (enable)' },
|
|
6433
6592
|
momentum: { type: 'boolean', description: 'Drag-spin inertia (enable)' },
|
|
6434
6593
|
showOcean: { type: 'boolean', description: 'Render ocean sphere (enable)' },
|
|
@@ -6472,7 +6631,8 @@ ACTIONS:
|
|
|
6472
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:
|
|
6473
6632
|
|
|
6474
6633
|
- list_available — { category?, loadedOnly? } every family the studio can render, with whether its file is loaded yet
|
|
6475
|
-
-
|
|
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.
|
|
6476
6636
|
- fallbacks — {} which text items on the canvas are silently drawing in something other than the family they asked for
|
|
6477
6637
|
|
|
6478
6638
|
ACTIONS (authoring):
|
|
@@ -6498,7 +6658,7 @@ ACTIONS (authoring):
|
|
|
6498
6658
|
action: {
|
|
6499
6659
|
type: 'string',
|
|
6500
6660
|
enum: [
|
|
6501
|
-
'list_available', 'check', 'fallbacks',
|
|
6661
|
+
'list_available', 'check', 'load', 'fallbacks',
|
|
6502
6662
|
'show_studio', 'set_name', 'get_required_chars', 'get_status',
|
|
6503
6663
|
'create_glyph', 'create_space', 'remove_glyph', 'set_metrics',
|
|
6504
6664
|
'export', 'load_into_document', 'export_data', 'import_data',
|
|
@@ -6509,7 +6669,8 @@ ACTIONS (authoring):
|
|
|
6509
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.' },
|
|
6510
6670
|
category: { type: 'string', description: 'list_available: narrow to one category (e.g. display, serif, mono).' },
|
|
6511
6671
|
loadedOnly: { type: 'boolean', description: 'list_available: only families whose font file has already loaded. Unloaded ones still render once the studio fetches them.' },
|
|
6512
|
-
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.' },
|
|
6513
6674
|
set: { type: 'string', description: 'Character set name (get_required_chars; default "minimum")' },
|
|
6514
6675
|
character: { type: 'string', description: 'Single-character glyph identifier (create_glyph, remove_glyph)' },
|
|
6515
6676
|
pathId: { type: 'string', description: 'Item id of the path source (create_glyph, remove_overlap, correct_direction, cleanup_path)' },
|
|
@@ -6772,7 +6933,7 @@ New — Geometric:
|
|
|
6772
6933
|
- drawGeometricAbstract: Mixed translucent shapes (colors, shapeCount, blendMode, rotation)
|
|
6773
6934
|
|
|
6774
6935
|
New — Particles:
|
|
6775
|
-
- 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)
|
|
6776
6937
|
|
|
6777
6938
|
New — Textures:
|
|
6778
6939
|
- drawNoiseTexture: Perlin/grain/stipple noise (noiseType: perlin|grain|stipple, colors, scale, density, animated)
|
|
@@ -6795,7 +6956,7 @@ S6 Procedural (seeded, OKLCH palettes, curated presets, optional Motion). All ta
|
|
|
6795
6956
|
GPU / GLSL math-art (transpiled to a fragment shader, 60fps; all take renderScale 0.25–1, transparentBg, speed):
|
|
6796
6957
|
- drawFormulaArt: type a math expression in x,y,t → scalar mapped through a 3-stop palette (formula, domain, color1/color2/color3). Try sin(x*5+t)*cos(y*3).
|
|
6797
6958
|
- drawParametricCollection: N circles or line segments indexed by k, expressions in k,N,t — Yeganeh-style (mode: circles|lines, count, xExpression/yExpression/radiusExpression or x1..y2Expression, scale, palette, bgColor).
|
|
6798
|
-
- drawShaderArt:
|
|
6959
|
+
- drawShaderArt: a GLSL fragment shader. PASSING fragmentSource ALONE DOES NOTHING — preset defaults to 'wavefield' and wins, so your shader is ignored and a preset renders instead, reporting success. Pass preset: 'custom' to use fragmentSource verbatim. The shader is GLSL ES 3.00: inputs 'in vec2 v_uv', output 'out vec4 outColor' — NOT gl_FragColor, and there is no u_uv. Uniforms: u_time, u_resolution (vec2), u_color1/2/3 (vec3). Other presets: wavefield, gradient_glow, noise_clouds, voronoi, simplex_noise, perlin, pulsing_border. renderScale 0.25-1 (default 0.5) trades resolution for speed.
|
|
6799
6960
|
- drawYeganehMountains: GLSL port of Hamid Naderi Yeganeh's "Mountains" (mountainCount, timeOffset, skyTop, skyBottom, ridgeColor, valleyColor).
|
|
6800
6961
|
|
|
6801
6962
|
OKLCH seeded backgrounds (palette + seed → reproducible):
|
|
@@ -6825,14 +6986,19 @@ COMMON PARAMS (all generators):
|
|
|
6825
6986
|
},
|
|
6826
6987
|
region: {
|
|
6827
6988
|
type: 'object',
|
|
6828
|
-
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.',
|
|
6829
6990
|
properties: {
|
|
6830
6991
|
x: { type: 'number', description: 'Region top-left X in canvas coords' },
|
|
6831
6992
|
y: { type: 'number', description: 'Region top-left Y in canvas coords' },
|
|
6832
6993
|
width: { type: 'number', minimum: 1, description: 'Region width in pixels' },
|
|
6833
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.' },
|
|
6834
7001
|
},
|
|
6835
|
-
required: ['x', 'y', 'width', 'height'],
|
|
6836
7002
|
},
|
|
6837
7003
|
},
|
|
6838
7004
|
required: ['generatorName'],
|
|
@@ -6879,6 +7045,15 @@ USE WHEN:
|
|
|
6879
7045
|
- Celebration effects (confetti)
|
|
6880
7046
|
- Enhancing visual impact (ripple, glow, electric)
|
|
6881
7047
|
|
|
7048
|
+
SIZE IS A MULTIPLIER, NOT PIXELS — and this is the one that bites hardest.
|
|
7049
|
+
Every effect derives its scale from the ITEM'S OWN BOUNDS, and 'size' multiplies
|
|
7050
|
+
that. glow computes a radius from max(width, height) and pulses it to
|
|
7051
|
+
'1 + (size - 1)', so size: 30 on a modest item asks for a disc thousands of
|
|
7052
|
+
pixels across, gets one, and reports success. Typical values are 1.5 to 3 —
|
|
7053
|
+
glow's own default is 1.5. Pass a pixel count here and you will be wrong by
|
|
7054
|
+
about two orders of magnitude. A recent studio warns about it
|
|
7055
|
+
(kind: 'glow-size-unit'); an older one does not.
|
|
7056
|
+
|
|
6882
7057
|
EFFECTS (15 particle + 7 shader auras):
|
|
6883
7058
|
- sparkle: Glitter/sparkle particles (color, speed, size)
|
|
6884
7059
|
- blast: Explosion burst effect (color, radius, count)
|
|
@@ -6955,13 +7130,14 @@ AVAILABLE FILTERS:
|
|
|
6955
7130
|
- blur: Gaussian blur effect (params: radius 0-20)
|
|
6956
7131
|
- brightness: Adjust brightness (params: value -100 to 100)
|
|
6957
7132
|
- contrast: Adjust contrast (params: value -100 to 100)
|
|
6958
|
-
-
|
|
7133
|
+
- saturation: Adjust color saturation (params: value -100 to 100). 'saturate' is accepted as the same filter.
|
|
6959
7134
|
- invert: Invert colors (params: intensity 0-1)
|
|
6960
7135
|
- noise: Add film grain (params: intensity 0-100, monochrome: true/false)
|
|
6961
7136
|
- vignette: Darken edges (params: intensity 0-1, radius 0-1)
|
|
6962
7137
|
- vintage: Preset combining sepia, vignette, noise
|
|
6963
7138
|
- colorOverlay: Add color tint (params: color, intensity, blendMode)
|
|
6964
7139
|
- sharpen: Increase sharpness (params: intensity 0-100)
|
|
7140
|
+
- emboss: 3D emboss (params: intensity 0-1)
|
|
6965
7141
|
- posterize: Reduce color levels (params: levels 2-32)
|
|
6966
7142
|
|
|
6967
7143
|
EXAMPLES:
|
|
@@ -6976,7 +7152,7 @@ Filters can be stacked - call multiple times to combine effects.`,
|
|
|
6976
7152
|
properties: {
|
|
6977
7153
|
filterType: {
|
|
6978
7154
|
type: 'string',
|
|
6979
|
-
enum: ['grayscale', 'sepia', 'blur', 'brightness', 'contrast', '
|
|
7155
|
+
enum: ['grayscale', 'sepia', 'blur', 'brightness', 'contrast', 'saturation', 'invert', 'noise', 'vignette', 'vintage', 'colorOverlay', 'sharpen', 'emboss', 'posterize', 'saturate'],
|
|
6980
7156
|
description: 'Type of filter to apply',
|
|
6981
7157
|
},
|
|
6982
7158
|
params: {
|
|
@@ -7408,7 +7584,7 @@ WORKFLOW: start_job → batch_execute (everything in ONE call) → end_job (scre
|
|
|
7408
7584
|
|
|
7409
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.
|
|
7410
7586
|
|
|
7411
|
-
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)`,
|
|
7412
7588
|
inputSchema: {
|
|
7413
7589
|
type: 'object',
|
|
7414
7590
|
properties: {
|
|
@@ -7422,12 +7598,12 @@ CANVAS PRESETS: instagram (1080x1080), instagram-story (1080x1920), tiktok (1080
|
|
|
7422
7598
|
},
|
|
7423
7599
|
screenshotPolicy: {
|
|
7424
7600
|
type: 'string',
|
|
7425
|
-
enum: ['on_complete', 'on_error', '
|
|
7426
|
-
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'.",
|
|
7427
7603
|
},
|
|
7428
7604
|
canvasPreset: {
|
|
7429
7605
|
type: 'string',
|
|
7430
|
-
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'],
|
|
7431
7607
|
description: 'Set canvas size to platform preset',
|
|
7432
7608
|
},
|
|
7433
7609
|
clearCanvas: {
|
|
@@ -7534,7 +7710,7 @@ EXAMPLES:
|
|
|
7534
7710
|
properties: {
|
|
7535
7711
|
canvasPreset: {
|
|
7536
7712
|
type: 'string',
|
|
7537
|
-
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'],
|
|
7538
7714
|
description: 'Set canvas size to platform preset after reset',
|
|
7539
7715
|
},
|
|
7540
7716
|
backgroundColor: {
|
|
@@ -7563,7 +7739,7 @@ NOTE: For diagrams (flowcharts, UML), use pinepaper_create_diagram_shape + pinep
|
|
|
7563
7739
|
|
|
7564
7740
|
⚠️ EVERY operation executes LIVE on the canvas. Calling this twice DOUBLES all items. Call ONCE per pipeline.
|
|
7565
7741
|
|
|
7566
|
-
⚠️ 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.
|
|
7567
7743
|
|
|
7568
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.
|
|
7569
7745
|
|
|
@@ -7599,8 +7775,8 @@ ANIMATION:
|
|
|
7599
7775
|
animate — Loop animation: {itemId, animationType, animationOptions}
|
|
7600
7776
|
animationType: ${SIMPLE_ANIMATION_TYPES_LIST}
|
|
7601
7777
|
keyframe_animate — Timed animation: {itemId, keyframes: [{time, properties, easing}], duration, loop}
|
|
7602
|
-
Animatable properties: opacity, scale, scaleX, scaleY, x, y, rotation, fillColor, strokeColor, fontSize
|
|
7603
|
-
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}
|
|
7604
7780
|
relation — Behavioral link: {sourceId, targetId, relationType, relationOptions}
|
|
7605
7781
|
relationType: orbits, follows, attached_to, points_at, mirrors, parallax, wave_through, morphs_to, group_morphs_to, moves_along_path (+ 9 more in schema)
|
|
7606
7782
|
|
|
@@ -7650,8 +7826,8 @@ EXAMPLE — Animated sky scene with timed reveals:
|
|
|
7650
7826
|
description: 'Operation type',
|
|
7651
7827
|
},
|
|
7652
7828
|
// Canvas setup
|
|
7653
|
-
width: { type: 'number', description: 'Canvas width for set_canvas_size (100-
|
|
7654
|
-
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)' },
|
|
7655
7831
|
preset: { type: 'string', description: 'Canvas preset for set_canvas_size' },
|
|
7656
7832
|
backgroundColor: { type: 'string', description: 'For set_background: hex color' },
|
|
7657
7833
|
generatorName: {
|
|
@@ -7803,7 +7979,10 @@ Held exports last until released, or until a LATER export needs the space — an
|
|
|
7803
7979
|
|
|
7804
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.
|
|
7805
7981
|
|
|
7806
|
-
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.
|
|
7807
7986
|
|
|
7808
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.
|
|
7809
7988
|
|
|
@@ -7817,6 +7996,8 @@ PLATFORMS & OPTIMAL FORMATS:
|
|
|
7817
7996
|
| twitter | 1200x675 | PNG | GIF |
|
|
7818
7997
|
| web | flexible | SVG | SVG |
|
|
7819
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
|
+
|
|
7820
8001
|
FORMATS: svg, png, gif, mp4, webm, pdf.
|
|
7821
8002
|
QUALITY: draft, standard, high.
|
|
7822
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).
|
|
@@ -7831,19 +8012,21 @@ EXAMPLES:
|
|
|
7831
8012
|
- {platform: "youtube", format: "mp4", framing: "camera"} — render only what the camera frames during the walkthrough
|
|
7832
8013
|
- {platform: "youtube", format: "mp4", duration: 30, estimateOnly: true} — how big would that be, and what would it lose? (renders nothing)
|
|
7833
8014
|
|
|
7834
|
-
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.`,
|
|
7835
8018
|
inputSchema: {
|
|
7836
8019
|
type: 'object',
|
|
7837
8020
|
properties: {
|
|
7838
8021
|
platform: {
|
|
7839
8022
|
type: 'string',
|
|
7840
|
-
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'],
|
|
7841
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.",
|
|
7842
8025
|
},
|
|
7843
8026
|
format: {
|
|
7844
8027
|
type: 'string',
|
|
7845
|
-
enum: ['svg', 'png', 'gif', 'mp4', 'webm', 'pdf', 'wav'],
|
|
7846
|
-
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.",
|
|
7847
8030
|
},
|
|
7848
8031
|
sampleRate: {
|
|
7849
8032
|
type: 'number',
|
|
@@ -7854,22 +8037,62 @@ Every export answers the second question too: where this scene loses something t
|
|
|
7854
8037
|
enum: [16, 32],
|
|
7855
8038
|
description: 'wav only: 16 (default) or 32-bit float. Rejected for any other format.',
|
|
7856
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 only: keep the alpha channel (the default); false fills the background colour.' },
|
|
8050
|
+
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.' },
|
|
8051
|
+
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.' },
|
|
8052
|
+
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.' },
|
|
8053
|
+
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.' },
|
|
8054
|
+
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.' },
|
|
8055
|
+
bitrateMode: { type: 'string', enum: ['constant', 'variable'], description: "mp4 / webm: 'constant' (the default with broadcast) or 'variable'." },
|
|
8056
|
+
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.' },
|
|
8057
|
+
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.' },
|
|
8058
|
+
pdf: {
|
|
8059
|
+
type: 'object',
|
|
8060
|
+
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.",
|
|
8061
|
+
properties: {
|
|
8062
|
+
paperFormat: { type: 'string' },
|
|
8063
|
+
orientation: { type: 'string', enum: ['portrait', 'landscape'] },
|
|
8064
|
+
bleed: { type: 'number', minimum: 0, maximum: 20 },
|
|
8065
|
+
trimMarks: { type: 'boolean' },
|
|
8066
|
+
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.' },
|
|
8067
|
+
dpi: { type: 'integer', minimum: 72, maximum: 600 },
|
|
8068
|
+
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." },
|
|
8069
|
+
},
|
|
8070
|
+
},
|
|
8071
|
+
region: {
|
|
8072
|
+
type: 'object',
|
|
8073
|
+
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}.',
|
|
8074
|
+
properties: {
|
|
8075
|
+
x: { type: 'number' }, y: { type: 'number' },
|
|
8076
|
+
width: { type: 'number', exclusiveMinimum: 0 }, height: { type: 'number', exclusiveMinimum: 0 },
|
|
8077
|
+
outputWidth: { type: 'integer', minimum: 1, maximum: 8192 }, outputHeight: { type: 'integer', minimum: 1, maximum: 8192 },
|
|
8078
|
+
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.' },
|
|
8079
|
+
},
|
|
8080
|
+
required: ['x', 'y', 'width', 'height'],
|
|
8081
|
+
},
|
|
7857
8082
|
quality: {
|
|
7858
8083
|
type: 'string',
|
|
7859
8084
|
enum: ['draft', 'standard', 'high'],
|
|
7860
8085
|
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.',
|
|
7861
8086
|
},
|
|
7862
8087
|
fps: {
|
|
7863
|
-
type: '
|
|
7864
|
-
|
|
7865
|
-
maximum: 120,
|
|
7866
|
-
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.',
|
|
8088
|
+
anyOf: [{ type: 'number', minimum: 1, maximum: 120 }, { type: 'string', pattern: '^[0-9]+/[0-9]+$' }],
|
|
8089
|
+
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.',
|
|
7867
8090
|
},
|
|
7868
8091
|
scale: {
|
|
7869
8092
|
type: 'number',
|
|
7870
8093
|
minimum: 0.1,
|
|
7871
8094
|
maximum: 1,
|
|
7872
|
-
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
|
|
8095
|
+
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.',
|
|
7873
8096
|
},
|
|
7874
8097
|
framing: {
|
|
7875
8098
|
type: 'string',
|
|
@@ -8441,6 +8664,7 @@ DATA FORMAT: Array of objects, e.g. [{category: "A", value: 10}, {category: "B",
|
|
|
8441
8664
|
|
|
8442
8665
|
OPTIONS (vary by chart type):
|
|
8443
8666
|
- Common: title, width, height, x, y, colors, animation, axisColor, labelColor, labelFontSize
|
|
8667
|
+
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.
|
|
8444
8668
|
- Bar: barWidth, barGap, xLabel, yLabel
|
|
8445
8669
|
orient: 'horizontal' flips bars to grow rightward from a left Y-axis — best for long category names
|
|
8446
8670
|
labelArrangement: 'auto'|'horizontal'|'slanted'|'vertical'|'truncate'|'wrap'
|