@pixerate/editor 0.61.0 → 0.62.0
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/THIRD_PARTY_NOTICES.md +2 -0
- package/dist/animation-editor/index.cjs +75 -16
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +31 -3
- package/dist/animation-editor/index.d.ts +31 -3
- package/dist/animation-editor/index.js +74 -15
- package/dist/animation-editor/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -75,6 +75,11 @@ interface MotionShot {
|
|
|
75
75
|
run: MotionCommand[];
|
|
76
76
|
/** Milliseconds to hold after the commands finish (default 600). */
|
|
77
77
|
hold?: number;
|
|
78
|
+
/**
|
|
79
|
+
* Plays the whole shot this many times as fast (default 1): its camera move, delays,
|
|
80
|
+
* durations, staggers and hold all scale by 1 / speed. `fit_animation` sets it to fit a length.
|
|
81
|
+
*/
|
|
82
|
+
speed?: number;
|
|
78
83
|
}
|
|
79
84
|
/** Fields every command takes. */
|
|
80
85
|
interface MotionCommandBase {
|
|
@@ -730,6 +735,8 @@ declare function fitMotionRect(rect: MotionRect, aspect: number): MotionRect;
|
|
|
730
735
|
declare function resolveMotionCamera(camera: MotionCamera, scene: MotionScene): MotionRect | null;
|
|
731
736
|
/** Length of a node's outline (its longest path for a group), in document units. */
|
|
732
737
|
declare function motionPathLength(node: VectorNode, parentWorld?: AffineMatrix): number;
|
|
738
|
+
/** How much a shot's `speed` stretches its time (1 / speed; 1 when unset or not above 0). */
|
|
739
|
+
declare function motionShotTimeScale(shot: MotionShot): number;
|
|
733
740
|
/** How many times a command plays (1 for `enter`, `exit` and `wait`, which do not repeat). */
|
|
734
741
|
declare function motionCommandRepeat(command: MotionCommand): number;
|
|
735
742
|
/** How long one target of a command takes (ms), every repeat included. */
|
|
@@ -877,7 +884,7 @@ type MotionEdit = {
|
|
|
877
884
|
op: 'update_shot';
|
|
878
885
|
shot: string;
|
|
879
886
|
set?: Partial<MotionShot>;
|
|
880
|
-
remove?: ('label' | 'camera' | 'hold')[];
|
|
887
|
+
remove?: ('label' | 'camera' | 'hold' | 'speed')[];
|
|
881
888
|
} | {
|
|
882
889
|
op: 'delete_shots';
|
|
883
890
|
shots: string[];
|
|
@@ -931,6 +938,27 @@ declare function applyMotionEdits(scene: MotionScene, edits: MotionEdit | readon
|
|
|
931
938
|
/** An empty scene over `artwork`. */
|
|
932
939
|
declare function createMotionScene(artwork: VectorDocument, init?: Partial<Omit<MotionScene, 'artwork' | 'version'>>): MotionScene;
|
|
933
940
|
|
|
941
|
+
interface MotionFitOptions {
|
|
942
|
+
/** Shot ids that keep their pace (a logo hold, a line of narration); the others absorb the change. */
|
|
943
|
+
keep?: readonly string[];
|
|
944
|
+
}
|
|
945
|
+
interface MotionFit {
|
|
946
|
+
scene: MotionScene;
|
|
947
|
+
/** The length after fitting, ms (within a few ms of the target: speeds are rounded). */
|
|
948
|
+
duration: number;
|
|
949
|
+
/** The new `speed` of every shot that changed. */
|
|
950
|
+
speeds: Record<string, number>;
|
|
951
|
+
}
|
|
952
|
+
/** Speeds are kept within this range. */
|
|
953
|
+
declare const MOTION_SPEED_RANGE: readonly [number, number];
|
|
954
|
+
/**
|
|
955
|
+
* Fits a scene to `ms` by setting its shots' `speed`: the shots in `keep` play as they are and
|
|
956
|
+
* the rest stretch or squeeze by the same factor, so their timing keeps its proportions (the
|
|
957
|
+
* responsive time of After Effects' protected regions). Throws, changing nothing, when the
|
|
958
|
+
* kept shots alone outlast the target or a speed would leave `MOTION_SPEED_RANGE`.
|
|
959
|
+
*/
|
|
960
|
+
declare function fitMotionScene(scene: MotionScene, ms: number, options?: MotionFitOptions): MotionFit;
|
|
961
|
+
|
|
934
962
|
/**
|
|
935
963
|
* Export to Flair (`@pixerate/flair`): a scene becomes an `SvgMotion` (one SVG plus a list of
|
|
936
964
|
* from → to changes on its elements), a catalog `VectorEffect`, or an `EffectDocument` for
|
|
@@ -1675,6 +1703,6 @@ declare function createAnimationEditorToolProvider(controller: AnimationEditorCo
|
|
|
1675
1703
|
* A short authoring guide for a model writing vector animations with the animation editor's
|
|
1676
1704
|
* tools. Static, so it caches well as part of a system prompt.
|
|
1677
1705
|
*/
|
|
1678
|
-
declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- enter also reveals through a clip over the target's box: wipe (behind an edge travelling \"direction\", default right), iris (from the centre; \"shape\": circle or box) and split (from a line through the middle; \"orientation\": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.\n- draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale \"blend\": \"add\" to stack them too (what they add stays); \"blend\": \"replace\" makes a change take over instead.\n- Any command but enter and exit can \"repeat\" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); \"duration\" is one cycle.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- \"steps\": 12 (on a command, or for the whole scene with set_animation_settings) holds poses \"on twos\" for a hand-drawn look. \"geometric\": true on scale or a camera zooms at an even rate.\n- Commands share their names and defaults with Flair's motion primitives (get_animation shows each one's \"flairPrimitive\"), and emphasis, reveals and follows export to Flair exactly.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
|
|
1706
|
+
declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- enter also reveals through a clip over the target's box: wipe (behind an edge travelling \"direction\", default right), iris (from the centre; \"shape\": circle or box) and split (from a line through the middle; \"orientation\": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.\n- draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale \"blend\": \"add\" to stack them too (what they add stays); \"blend\": \"replace\" makes a change take over instead.\n- Any command but enter and exit can \"repeat\" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); \"duration\" is one cycle.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- \"steps\": 12 (on a command, or for the whole scene with set_animation_settings) holds poses \"on twos\" for a hand-drawn look. \"geometric\": true on scale or a camera zooms at an even rate.\n- Commands share their names and defaults with Flair's motion primitives (get_animation shows each one's \"flairPrimitive\"), and emphasis, reveals and follows export to Flair exactly.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
|
|
1679
1707
|
|
|
1680
|
-
export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPLIT_ORIENTATIONS, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNodeEntry, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
|
|
1708
|
+
export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPEED_RANGE, MOTION_SPLIT_ORIENTATIONS, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFit, type MotionFitOptions, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNodeEntry, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, fitMotionScene, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotTimeScale, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
|
|
@@ -75,6 +75,11 @@ interface MotionShot {
|
|
|
75
75
|
run: MotionCommand[];
|
|
76
76
|
/** Milliseconds to hold after the commands finish (default 600). */
|
|
77
77
|
hold?: number;
|
|
78
|
+
/**
|
|
79
|
+
* Plays the whole shot this many times as fast (default 1): its camera move, delays,
|
|
80
|
+
* durations, staggers and hold all scale by 1 / speed. `fit_animation` sets it to fit a length.
|
|
81
|
+
*/
|
|
82
|
+
speed?: number;
|
|
78
83
|
}
|
|
79
84
|
/** Fields every command takes. */
|
|
80
85
|
interface MotionCommandBase {
|
|
@@ -730,6 +735,8 @@ declare function fitMotionRect(rect: MotionRect, aspect: number): MotionRect;
|
|
|
730
735
|
declare function resolveMotionCamera(camera: MotionCamera, scene: MotionScene): MotionRect | null;
|
|
731
736
|
/** Length of a node's outline (its longest path for a group), in document units. */
|
|
732
737
|
declare function motionPathLength(node: VectorNode, parentWorld?: AffineMatrix): number;
|
|
738
|
+
/** How much a shot's `speed` stretches its time (1 / speed; 1 when unset or not above 0). */
|
|
739
|
+
declare function motionShotTimeScale(shot: MotionShot): number;
|
|
733
740
|
/** How many times a command plays (1 for `enter`, `exit` and `wait`, which do not repeat). */
|
|
734
741
|
declare function motionCommandRepeat(command: MotionCommand): number;
|
|
735
742
|
/** How long one target of a command takes (ms), every repeat included. */
|
|
@@ -877,7 +884,7 @@ type MotionEdit = {
|
|
|
877
884
|
op: 'update_shot';
|
|
878
885
|
shot: string;
|
|
879
886
|
set?: Partial<MotionShot>;
|
|
880
|
-
remove?: ('label' | 'camera' | 'hold')[];
|
|
887
|
+
remove?: ('label' | 'camera' | 'hold' | 'speed')[];
|
|
881
888
|
} | {
|
|
882
889
|
op: 'delete_shots';
|
|
883
890
|
shots: string[];
|
|
@@ -931,6 +938,27 @@ declare function applyMotionEdits(scene: MotionScene, edits: MotionEdit | readon
|
|
|
931
938
|
/** An empty scene over `artwork`. */
|
|
932
939
|
declare function createMotionScene(artwork: VectorDocument, init?: Partial<Omit<MotionScene, 'artwork' | 'version'>>): MotionScene;
|
|
933
940
|
|
|
941
|
+
interface MotionFitOptions {
|
|
942
|
+
/** Shot ids that keep their pace (a logo hold, a line of narration); the others absorb the change. */
|
|
943
|
+
keep?: readonly string[];
|
|
944
|
+
}
|
|
945
|
+
interface MotionFit {
|
|
946
|
+
scene: MotionScene;
|
|
947
|
+
/** The length after fitting, ms (within a few ms of the target: speeds are rounded). */
|
|
948
|
+
duration: number;
|
|
949
|
+
/** The new `speed` of every shot that changed. */
|
|
950
|
+
speeds: Record<string, number>;
|
|
951
|
+
}
|
|
952
|
+
/** Speeds are kept within this range. */
|
|
953
|
+
declare const MOTION_SPEED_RANGE: readonly [number, number];
|
|
954
|
+
/**
|
|
955
|
+
* Fits a scene to `ms` by setting its shots' `speed`: the shots in `keep` play as they are and
|
|
956
|
+
* the rest stretch or squeeze by the same factor, so their timing keeps its proportions (the
|
|
957
|
+
* responsive time of After Effects' protected regions). Throws, changing nothing, when the
|
|
958
|
+
* kept shots alone outlast the target or a speed would leave `MOTION_SPEED_RANGE`.
|
|
959
|
+
*/
|
|
960
|
+
declare function fitMotionScene(scene: MotionScene, ms: number, options?: MotionFitOptions): MotionFit;
|
|
961
|
+
|
|
934
962
|
/**
|
|
935
963
|
* Export to Flair (`@pixerate/flair`): a scene becomes an `SvgMotion` (one SVG plus a list of
|
|
936
964
|
* from → to changes on its elements), a catalog `VectorEffect`, or an `EffectDocument` for
|
|
@@ -1675,6 +1703,6 @@ declare function createAnimationEditorToolProvider(controller: AnimationEditorCo
|
|
|
1675
1703
|
* A short authoring guide for a model writing vector animations with the animation editor's
|
|
1676
1704
|
* tools. Static, so it caches well as part of a system prompt.
|
|
1677
1705
|
*/
|
|
1678
|
-
declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- enter also reveals through a clip over the target's box: wipe (behind an edge travelling \"direction\", default right), iris (from the centre; \"shape\": circle or box) and split (from a line through the middle; \"orientation\": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.\n- draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale \"blend\": \"add\" to stack them too (what they add stays); \"blend\": \"replace\" makes a change take over instead.\n- Any command but enter and exit can \"repeat\" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); \"duration\" is one cycle.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- \"steps\": 12 (on a command, or for the whole scene with set_animation_settings) holds poses \"on twos\" for a hand-drawn look. \"geometric\": true on scale or a camera zooms at an even rate.\n- Commands share their names and defaults with Flair's motion primitives (get_animation shows each one's \"flairPrimitive\"), and emphasis, reveals and follows export to Flair exactly.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
|
|
1706
|
+
declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- enter also reveals through a clip over the target's box: wipe (behind an edge travelling \"direction\", default right), iris (from the centre; \"shape\": circle or box) and split (from a line through the middle; \"orientation\": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.\n- draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale \"blend\": \"add\" to stack them too (what they add stays); \"blend\": \"replace\" makes a change take over instead.\n- Any command but enter and exit can \"repeat\" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); \"duration\" is one cycle.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- \"steps\": 12 (on a command, or for the whole scene with set_animation_settings) holds poses \"on twos\" for a hand-drawn look. \"geometric\": true on scale or a camera zooms at an even rate.\n- Commands share their names and defaults with Flair's motion primitives (get_animation shows each one's \"flairPrimitive\"), and emphasis, reveals and follows export to Flair exactly.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
|
|
1679
1707
|
|
|
1680
|
-
export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPLIT_ORIENTATIONS, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNodeEntry, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
|
|
1708
|
+
export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPEED_RANGE, MOTION_SPLIT_ORIENTATIONS, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFit, type MotionFitOptions, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNodeEntry, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, fitMotionScene, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotTimeScale, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
|
|
@@ -207,7 +207,7 @@ ${problems.map((p) => `- ${p}`).join("\n")}`);
|
|
|
207
207
|
}
|
|
208
208
|
};
|
|
209
209
|
var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "loop", "fps", "steps", "target", "controls", "presets"];
|
|
210
|
-
var SHOT_KEYS = ["id", "label", "camera", "run", "hold"];
|
|
210
|
+
var SHOT_KEYS = ["id", "label", "camera", "run", "hold", "speed"];
|
|
211
211
|
var CAMERA_KEYS = ["x", "y", "width", "height", "frame", "padding", "ease", "ms", "geometric"];
|
|
212
212
|
var BASE_KEYS = ["type", "targets", "duration", "delay", "stagger", "with", "ease", "note", "repeat", "repeatMode", "steps"];
|
|
213
213
|
var COMMAND_KEYS = {
|
|
@@ -360,6 +360,7 @@ function shotShapeProblems(shot, where) {
|
|
|
360
360
|
const name = typeof shot.id === "string" ? `Shot "${shot.id}"` : where;
|
|
361
361
|
if (shot.label !== void 0 && typeof shot.label !== "string") problems.push(`${name}: "label" must be a string.`);
|
|
362
362
|
if (shot.hold !== void 0 && !(isNum(shot.hold) && shot.hold >= 0)) problems.push(`${name}: "hold" must be a number of milliseconds \u2265 0.`);
|
|
363
|
+
if (shot.speed !== void 0 && !(isNum(shot.speed) && shot.speed >= 0.05 && shot.speed <= 20)) problems.push(`${name}: "speed" must be a number from 0.05 to 20 (2 plays it twice as fast).`);
|
|
363
364
|
if (shot.camera !== void 0) {
|
|
364
365
|
const c = shot.camera;
|
|
365
366
|
if (!isObject(c)) problems.push(`${name}: "camera" must be an object.`);
|
|
@@ -695,6 +696,9 @@ function motionPathLength(node, parentWorld = IDENTITY_MATRIX) {
|
|
|
695
696
|
}
|
|
696
697
|
return longest;
|
|
697
698
|
}
|
|
699
|
+
function motionShotTimeScale(shot) {
|
|
700
|
+
return typeof shot.speed === "number" && shot.speed > 0 && Number.isFinite(shot.speed) ? 1 / shot.speed : 1;
|
|
701
|
+
}
|
|
698
702
|
function motionCommandRepeat(command2) {
|
|
699
703
|
if (command2.type === "wait" || command2.type === "enter" || command2.type === "exit") return 1;
|
|
700
704
|
const n = command2.repeat ?? 1;
|
|
@@ -734,10 +738,11 @@ function compileMotionScene(scene) {
|
|
|
734
738
|
scene.shots.forEach((shot, index) => {
|
|
735
739
|
const start = t;
|
|
736
740
|
let cursor = start;
|
|
741
|
+
const f = motionShotTimeScale(shot);
|
|
737
742
|
if (shot.camera) {
|
|
738
743
|
const rect = resolveMotionCamera(shot.camera, scene);
|
|
739
744
|
if (rect && rect.width > 0 && rect.height > 0) {
|
|
740
|
-
const ms = index === 0 ? 0 : shot.camera.ms ?? MOTION_CAMERA_MS;
|
|
745
|
+
const ms = index === 0 ? 0 : (shot.camera.ms ?? MOTION_CAMERA_MS) * f;
|
|
741
746
|
cameraMoves.push({ shot: shot.id, start, end: start + ms, to: rect, ease: shot.camera.ease ?? MOTION_EASE_IN_OUT, geometric: shot.camera.geometric === true });
|
|
742
747
|
cursor = start + ms;
|
|
743
748
|
}
|
|
@@ -747,16 +752,16 @@ function compileMotionScene(scene) {
|
|
|
747
752
|
let prevStart = cursor;
|
|
748
753
|
shot.run.forEach((command2, i) => {
|
|
749
754
|
const base = command2.with === "previous" && i > 0 ? prevStart : runEnd;
|
|
750
|
-
const begin = base + (command2.type === "wait" ? 0 : command2.delay ?? 0);
|
|
755
|
+
const begin = base + (command2.type === "wait" ? 0 : (command2.delay ?? 0) * f);
|
|
751
756
|
const targets = command2.type === "wait" ? [] : command2.targets;
|
|
752
|
-
const durations = command2.type === "wait" ? [command2.duration] : targets.map((id) => motionCommandDuration(command2, scene, id));
|
|
753
|
-
const stagger = command2.type === "wait" ? 0 : command2.stagger ?? 0;
|
|
757
|
+
const durations = command2.type === "wait" ? [command2.duration * f] : targets.map((id) => motionCommandDuration(command2, scene, id) * f);
|
|
758
|
+
const stagger = command2.type === "wait" ? 0 : (command2.stagger ?? 0) * f;
|
|
754
759
|
const end2 = durations.reduce((m, d, k) => Math.max(m, begin + k * stagger + d), begin);
|
|
755
760
|
commands.push({ shot: shot.id, shotIndex: index, command: i, type: command2.type, start: begin, end: end2, duration: durations[0] ?? 0 });
|
|
756
761
|
prevStart = begin;
|
|
757
762
|
runEnd = Math.max(runEnd, end2);
|
|
758
763
|
});
|
|
759
|
-
const end = runEnd + (shot.hold ?? MOTION_DEFAULT_HOLD);
|
|
764
|
+
const end = runEnd + (shot.hold ?? MOTION_DEFAULT_HOLD) * f;
|
|
760
765
|
shots.push({ id: shot.id, index, ...shot.label ? { label: shot.label } : {}, start, commandsStart, commandsEnd: runEnd, end });
|
|
761
766
|
t = end;
|
|
762
767
|
});
|
|
@@ -789,8 +794,9 @@ function compileMotionScene(scene) {
|
|
|
789
794
|
command2.targets.forEach((target, k) => {
|
|
790
795
|
const entry = nodes.get(target);
|
|
791
796
|
if (!entry) return;
|
|
792
|
-
const
|
|
793
|
-
const
|
|
797
|
+
const f = motionShotTimeScale(shot);
|
|
798
|
+
const start = timing.start + k * (command2.stagger ?? 0) * f;
|
|
799
|
+
const cycle = motionCycleDuration(command2, scene, target) * f;
|
|
794
800
|
jobs.push({ start, order: order++, run: () => compileRepeated(command2, target, entry, start, cycle, timing) });
|
|
795
801
|
});
|
|
796
802
|
}
|
|
@@ -1404,8 +1410,8 @@ function checkMotionScene(scene) {
|
|
|
1404
1410
|
if (scene.target && scene.shots.length) {
|
|
1405
1411
|
const seconds = Math.round(timeline.duration / 100) / 10;
|
|
1406
1412
|
const [min, max] = scene.target;
|
|
1407
|
-
if (seconds > max) issues.push(motionIssue("scene-too-long", `The scene runs ${seconds} s, past its ${max} s target; shorten holds or durations.`, { measured: seconds, needed: max, unit: "s" }));
|
|
1408
|
-
if (seconds < min) issues.push(motionIssue("scene-too-short", `The scene runs ${seconds} s, under its ${min} s target; hold longer or
|
|
1413
|
+
if (seconds > max) issues.push(motionIssue("scene-too-long", `The scene runs ${seconds} s, past its ${max} s target; shorten holds or durations, or fit it (fit_animation).`, { measured: seconds, needed: max, unit: "s" }));
|
|
1414
|
+
if (seconds < min) issues.push(motionIssue("scene-too-short", `The scene runs ${seconds} s, under its ${min} s target; hold longer, add shots, or fit it (fit_animation).`, { measured: seconds, needed: min, unit: "s" }));
|
|
1409
1415
|
}
|
|
1410
1416
|
return issues;
|
|
1411
1417
|
}
|
|
@@ -1590,6 +1596,34 @@ function createMotionScene(artwork, init = {}) {
|
|
|
1590
1596
|
return { version: 1, name: init.name ?? "animation", ...init, artwork, shots: init.shots ?? [] };
|
|
1591
1597
|
}
|
|
1592
1598
|
|
|
1599
|
+
// src/animation-editor/fit.ts
|
|
1600
|
+
var MOTION_SPEED_RANGE = [0.05, 20];
|
|
1601
|
+
function fitMotionScene(scene, ms, options = {}) {
|
|
1602
|
+
if (!(Number.isFinite(ms) && ms > 0)) throw new Error("Give the length to fit as a number of milliseconds above 0.");
|
|
1603
|
+
const ids = scene.shots.map((s) => s.id);
|
|
1604
|
+
const keep = new Set(options.keep ?? []);
|
|
1605
|
+
for (const id of keep) if (!ids.includes(id)) throw new Error(`There is no shot ${namedWithSuggestion(id, ids)} to keep.`);
|
|
1606
|
+
const timeline = compileMotionScene(scene);
|
|
1607
|
+
let kept = 0;
|
|
1608
|
+
let rest = 0;
|
|
1609
|
+
timeline.shots.forEach((t) => keep.has(t.id) ? kept += t.end - t.start : rest += t.end - t.start);
|
|
1610
|
+
if (rest <= 0) throw new Error(keep.size ? "Every shot is kept, so nothing can change pace; keep fewer shots." : "The scene has no length to fit; add shots first.");
|
|
1611
|
+
if (ms <= kept) throw new Error(`The kept shots alone last ${Math.round(kept)} ms, so the scene cannot fit ${Math.round(ms)} ms; keep fewer shots or give at least ${Math.ceil(kept) + 1} ms.`);
|
|
1612
|
+
const k = (ms - kept) / rest;
|
|
1613
|
+
const speeds = {};
|
|
1614
|
+
const shots = scene.shots.map((shot) => {
|
|
1615
|
+
if (keep.has(shot.id)) return shot;
|
|
1616
|
+
const speed = Math.round((shot.speed ?? 1) / k * 1e4) / 1e4;
|
|
1617
|
+
if (speed < MOTION_SPEED_RANGE[0] || speed > MOTION_SPEED_RANGE[1])
|
|
1618
|
+
throw new Error(`Fitting ${Math.round(ms)} ms would play shot "${shot.id}" at ${speed}\xD7 (speeds run ${MOTION_SPEED_RANGE[0]}\u2013${MOTION_SPEED_RANGE[1]}); aim nearer the current ${Math.round(timeline.duration)} ms.`);
|
|
1619
|
+
speeds[shot.id] = speed;
|
|
1620
|
+
const { speed: _old, ...rest2 } = shot;
|
|
1621
|
+
return speed === 1 ? rest2 : { ...rest2, speed };
|
|
1622
|
+
});
|
|
1623
|
+
const fitted = { ...scene, shots };
|
|
1624
|
+
return { scene: fitted, duration: compileMotionScene(fitted).duration, speeds };
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1593
1627
|
// src/animation-editor/flair.ts
|
|
1594
1628
|
var FLAIR_EASES = {
|
|
1595
1629
|
linear: (t) => t,
|
|
@@ -2460,7 +2494,8 @@ var SHOT_FIELDS = {
|
|
|
2460
2494
|
label: { type: "string", description: "What happens, in a few words." },
|
|
2461
2495
|
camera: CAMERA,
|
|
2462
2496
|
run: { type: "array", items: MOTION_COMMAND_SCHEMA, description: "Commands, in order." },
|
|
2463
|
-
hold: MS("Hold after the commands (default 600)")
|
|
2497
|
+
hold: MS("Hold after the commands (default 600)"),
|
|
2498
|
+
speed: { type: "number", minimum: 0.05, maximum: 20, description: "Play the whole shot this many times as fast (default 1; 0.5 is half speed). fit_animation sets it." }
|
|
2464
2499
|
};
|
|
2465
2500
|
var SHOT = object({ id: { type: "string", pattern: "^[A-Za-z0-9_][\\w.-]*$", description: 'Unique shot id, e.g. "intro".' }, ...SHOT_FIELDS }, ["id", "run"], "A shot.");
|
|
2466
2501
|
var CONTROL = object(
|
|
@@ -2487,7 +2522,7 @@ var EDIT_OP = {
|
|
|
2487
2522
|
oneOf: [
|
|
2488
2523
|
object({ op: { const: "set_settings", description: "Like set_animation_settings." }, ...SETTINGS }, ["op"]),
|
|
2489
2524
|
object({ op: { const: "insert_shots", description: "Like insert_shots." }, shots: { type: "array", items: SHOT, minItems: 1, description: "Shots to add." }, at: { type: "integer", minimum: 0, description: "Position (default: the end)." } }, ["op", "shots"]),
|
|
2490
|
-
object({ op: { const: "update_shot", description: "Like update_shot." }, shot: SHOT_ID, set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."), remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold"] }, description: "Fields to clear." } }, ["op", "shot"]),
|
|
2525
|
+
object({ op: { const: "update_shot", description: "Like update_shot." }, shot: SHOT_ID, set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."), remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed"] }, description: "Fields to clear." } }, ["op", "shot"]),
|
|
2491
2526
|
object({ op: { const: "delete_shots", description: "Like delete_shots." }, shots: { type: "array", items: SHOT_ID, minItems: 1, description: "Shot ids." } }, ["op", "shots"]),
|
|
2492
2527
|
object({ op: { const: "move_shot", description: "Like move_shot." }, shot: SHOT_ID, to: { type: "integer", minimum: 0, description: "New position (0 = first)." } }, ["op", "shot", "to"]),
|
|
2493
2528
|
object({ op: { const: "add_commands", description: "Like add_commands." }, shot: SHOT_ID, commands: { type: "array", items: MOTION_COMMAND_SCHEMA, minItems: 1, description: "Commands to add." }, at: { type: "integer", minimum: 0, description: "Index in the shot's run (default: the end)." } }, ["op", "shot", "commands"]),
|
|
@@ -2734,6 +2769,27 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2734
2769
|
return outcome(applied, applied.ok ? { ...report, elements: describeMotionElements(result.scene) } : {});
|
|
2735
2770
|
}
|
|
2736
2771
|
};
|
|
2772
|
+
const fitAnimation = {
|
|
2773
|
+
name: "fit_animation",
|
|
2774
|
+
title: "Fit the animation to a length",
|
|
2775
|
+
description: `Makes the animation last a given time by setting its shots' speed (one undo step): shots in "keep" play as they are and the others stretch or squeeze together, keeping their proportions. Returns the new length, each changed shot's speed and the timing. With dry_run it only reports them.`,
|
|
2776
|
+
inputSchema: object(
|
|
2777
|
+
{
|
|
2778
|
+
seconds: { type: "number", exclusiveMinimum: 0, description: "The length to fit, in seconds." },
|
|
2779
|
+
keep: { type: "array", items: SHOT_ID, description: "Shots that keep their pace, e.g. a logo hold (default none)." },
|
|
2780
|
+
dry_run: { type: "boolean", description: "Report without changing anything (default false)." }
|
|
2781
|
+
},
|
|
2782
|
+
["seconds"]
|
|
2783
|
+
),
|
|
2784
|
+
annotations: { title: "Fit the animation to a length", ...WRITE_IDEMPOTENT },
|
|
2785
|
+
handler: ({ seconds, keep, dry_run = false }) => {
|
|
2786
|
+
const fit = fitMotionScene(controller.getState().scene, seconds * 1e3, { keep: keep ?? [] });
|
|
2787
|
+
const report = { duration: fit.duration, speeds: fit.speeds };
|
|
2788
|
+
if (dry_run) return { dry_run: true, ...report, hint: "Nothing was changed; call again without dry_run to fit." };
|
|
2789
|
+
const edits = Object.entries(fit.speeds).map(([shot, speed]) => speed === 1 ? { op: "update_shot", shot, remove: ["speed"] } : { op: "update_shot", shot, set: { speed } });
|
|
2790
|
+
return apply(edits, "Fit to length", () => report);
|
|
2791
|
+
}
|
|
2792
|
+
};
|
|
2737
2793
|
const insertShots = {
|
|
2738
2794
|
name: "insert_shots",
|
|
2739
2795
|
title: "Add shots",
|
|
@@ -2750,7 +2806,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2750
2806
|
{
|
|
2751
2807
|
shot: SHOT_ID,
|
|
2752
2808
|
set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."),
|
|
2753
|
-
remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold"] }, description: "Fields to clear." }
|
|
2809
|
+
remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed"] }, description: "Fields to clear." }
|
|
2754
2810
|
},
|
|
2755
2811
|
["shot"]
|
|
2756
2812
|
),
|
|
@@ -2895,7 +2951,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
|
|
|
2895
2951
|
}
|
|
2896
2952
|
};
|
|
2897
2953
|
const reads = [getAnimation, getShot, findElements, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, seekAnimation];
|
|
2898
|
-
const writes = [setArtworkSvg, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
|
|
2954
|
+
const writes = [setArtworkSvg, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
|
|
2899
2955
|
return {
|
|
2900
2956
|
id: options.id ?? "animation",
|
|
2901
2957
|
namespace: options.namespace,
|
|
@@ -2940,7 +2996,7 @@ Commands
|
|
|
2940
2996
|
Working method
|
|
2941
2997
|
1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).
|
|
2942
2998
|
2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).
|
|
2943
|
-
3. check_animation and fix every error and warning; render_animation_frame (shot + "end") to look at the result.
|
|
2999
|
+
3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + "end") to look at the result.
|
|
2944
3000
|
4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.
|
|
2945
3001
|
|
|
2946
3002
|
Keep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.`;
|
|
@@ -2969,6 +3025,7 @@ export {
|
|
|
2969
3025
|
MOTION_ISSUE_CODES,
|
|
2970
3026
|
MOTION_MIN_CHANGE_MS,
|
|
2971
3027
|
MOTION_REPEAT_MODES,
|
|
3028
|
+
MOTION_SPEED_RANGE,
|
|
2972
3029
|
MOTION_SPLIT_ORIENTATIONS,
|
|
2973
3030
|
MotionSceneFormatError,
|
|
2974
3031
|
activeSegment,
|
|
@@ -2982,6 +3039,7 @@ export {
|
|
|
2982
3039
|
describeMotionElements,
|
|
2983
3040
|
describeMotionShots,
|
|
2984
3041
|
fitMotionRect,
|
|
3042
|
+
fitMotionScene,
|
|
2985
3043
|
flairAtLeast,
|
|
2986
3044
|
flairCurveFor,
|
|
2987
3045
|
flairEaseFor,
|
|
@@ -3014,6 +3072,7 @@ export {
|
|
|
3014
3072
|
motionSceneShapeProblems,
|
|
3015
3073
|
motionSceneToJson,
|
|
3016
3074
|
motionShotAt,
|
|
3075
|
+
motionShotTimeScale,
|
|
3017
3076
|
motionTrackKey,
|
|
3018
3077
|
namedWithSuggestion,
|
|
3019
3078
|
parseMotionScene,
|