Releases: johnhenry/ecmanim
Releases · johnhenry/ecmanim
Release list
v0.0.13
Added
sampleSceneAt(sceneOrConstruct, targetTime, config?)— replays a Scene'sconstruct()to a target time for scrubbing/embedding without a full render.glyphsFromDomSvg()— builds MathTex glyphs from CDN MathJax's real-DOMtex2svg()output, so vector MathTex now renders correctly in-browser.examples/e2e-feature-tour.ts— an end-to-end scene exercising the 0.0.7-0.0.12 feature surface through the real public package, which surfaced 4 real bugs (see Fixed).examples/compositor/— a browser-based scene-compositing/animation editor: layers/canvas/inspector/timeline, keyframe snapping, an independent play-range/loop marker, undo/redo, and ecmanim-source export.examples/studio-demo/— a runnable Studio dev-server example showing schema/props, camera stops, and property-keyframe tracks.
Fixed
FlexGroup'sflexGrow/flexShrinknow actually resize the child (#23).Circumscribe/Flash/FocusOnno longer render skewed under a 3D camera when targeting an already-fixed-in-frame mobject (#21).- HarfBuzz-shaped glyphs no longer render upside down.
SVGMobject's<linearGradient>fill no longer silently drops when combined with aclip-path.setTextShapingBackend()and the rest of the HarfBuzz text-shaping API are now exported from the public barrels.attachKeyframeTimelineEditor's drag no longer loses tracking when the pointer leaves the canvas mid-drag.
v0.0.12
0.0.12
Fixed
startStudio()was hardcoded to bind only to127.0.0.1, with no way to
configure it — unreachable from any device other than the one running
the dev server, which reads exactly like a firewall problem (connection
refused, not filtered) if you're viewing from another machine on the LAN
or over a remote/SSH-tunneled session. Added ahostoption (still
defaults to127.0.0.1— this dev server has no auth); passing
host: "0.0.0.0"binds wide-open.StudioHandlegainedurls: string[],
every address the server is actually reachable at (loopback + every
discovered LAN address when wildcard-bound), since a literal
http://0.0.0.0:PORT/URL isn't reliably browsable as-is.
v0.0.11
0.0.11
Added
- Page-transition playback resume (
ecmanim/browser):
enablePageTransitionResume(playerEl, opts?)carries a<manim-player>'s
playback position across a full page navigation — saves{ time }to
sessionStorageonpagehide, restores it viaseekTime()once the new
page's player fires "ready".savePlaybackPosition()/
restorePlaybackPosition()are the underlying pure functions if you want
to wire your own lifecycle hooks. Opt-in{ viewTransition: true }
additionally does a View Transitions snapshot handoff (canvases don't
participate in the browser's DOM-snapshot mechanism directly, so this
captures the outgoing frame into a plain<img>tagged with a shared
view-transition-name, and tags the incoming canvas with the same name). FlexGroup(src/mobject/flex_group.ts): opt-in real Flexbox layout
via Yoga (Meta/React's portable WASM
Flexbox engine), a newoptionalDependency(yoga-layout) mirroring
@napi-rs/canvas/three/harfbuzzjs's graceful-degrade pattern.
direction/justifyContent/alignItems/gapat the container level;
flexGrow/flexShrink/flexBasis/marginper child via
setChildFlex().await group.layout()builds a fresh Yoga node tree
from the group's current children and repositions them — necessarily
async (Yoga's WASM must load first), documented prominently as the one
sharp edge indocs/flex-group.md. Fully additive: mobjects outside a
FlexGroupare unaffected, and a child can still pin its own size.- WebGL raster-text batching (
ThreeRenderer): rasterText
(RasterText) mobjects now render as ONE shared texture atlas + ONE
merged quad mesh instead of oneTHREE.Sprite(ownCanvasTexture) per
mobject — converts N draw calls into 1. Newsrc/renderer/text_atlas.ts's
buildTextAtlas()does simple shelf-packing (sort tallest-first, pack
into rows). Scoped to a 2D-orthographic camera, where a flat quad is
visually identical to a billboarded sprite (the camera always looks
straight down -Z); a genuine 3D/perspective camera keeps the original
per-sprite path so real per-mobject billboarding still works. Falls back
to the per-sprite path gracefully wherever no synchronous canvas/document
backend is available (e.g. headless Node), same as the pre-existing
per-sprite code already did. Mobject.cacheStatic()+CanvasRendererstatic-subtree render cache:
an opt-in marker that, on an unchanged frame (content-based fingerprint
of geometry/style AND camera state — NOT reference equality, since
interpolate()mutatespointselement-by-element while keeping the
same outer array reference), blits a small cached offscreen bitmap
instead of re-walking the mobject's bezier path. Screen-space, MVP-scoped:
invalidated on any camera-state change, so it mainly helps static-camera
scenes with many unchanging elements (dense axis labels, background
grids), not continuous camera motion. Requires a synchronous
offscreen-canvas backend (OffscreenCanvasor a detached<canvas>
element) — gracefully no-ops (draws directly, same as always) under
Node/no-DOM, where only an async@napi-rs/canvasimport is available.- Property-keyframe Studio timeline:
Scene.track(keyframes)(mirrors
addSound()'s ergonomic) creates aPlayableKeyframeTrack
(src/reactive/keyframes.ts) — Cluster 2'sKeyframeTrackplus
absolute-timetick(dt)/seek(t), kept in exact agreement so authoring
playback and a Studio scrub can never drift apart.bindTrack(mobject, prop, track)wires a track's value onto a mobject property via the
ordinary updater mechanism — zeroScene/render changes needed for
playback correctness.scene.keyframeTracksmirrors the existing
sections/soundsarray pattern. NewcomputeKeyframeMarkers()/
renderKeyframeTimeline()/attachKeyframeTimelineEditor()
(src/studio/timeline.ts) draw a draggable per-track keyframe strip;
dragging updates a keyframe's time (keeping keyframes sorted), and a
debouncedonCommithook is meant to call item 7's parameter-only
re-render primitive (player.rerender()) to rebake frames, since
Player.frames[]are frozen bitmaps that a drag alone can't affect. - Rendered props panel (
startStudio({ props: true })): draws one
control perschemaToControls()descriptor, pre-filled from the schema's
own defaults. Edits are debounced (80ms) and re-render via
<manim-player>.rerender(props)→Player.record(scene, { props })
(parameter-only re-render, no re-import()), validated through
schema.safeParse()first. A real file-save reload still does a full
load()+ panel reset; the two triggers are kept structurally separate
(a rerender-triggered "ready" event carries the same schema object, so it
doesn't reset the panel). - Waveform visualization (
startStudio({ waveform: true })): draws a
bar-chart waveform strip below the live preview for each of the scene's
addSound()-scheduled sounds, positioned on the shared timeline via
src/studio/timeline.ts's newcomputeWaveformBars()/renderWaveform().
Reuses the existinggetAudioData()/getWaveformPortion()audio
primitives (both Node ffmpeg and browser AudioContext backends) — no new
audio decoding. Opt-in; off by default. - Parameter-only re-render primitive:
runConstruct(sceneOrConstruct, scene, props?)andPlayer.record(sceneOrConstruct, { props? })thread
propsthrough to a Scene subclass's ownconfig.propsor a bare
construct function's 2nd argument — both additive/opt-in. This still
re-runsconstruct()and re-records every frame; it doesn't itself avoid
that cost. Playerstep navigation:steps()/stepContaining()/seekToStep()/
nextStep()/prevStep(), mirroring the existing section-navigation
methods but readingscene.playRecords(finer-grained, independent of
section boundaries).<manim-player>'s presenter keydown handler now has
two tiers: plain Right/Left step; Shift+Right/Left (or PageDown/PageUp)
jump whole sections.Scene.nextSection()gains an optionalnotesparameter (and
SceneSection.notes) for presenter-mode speaker notes.Player.drawFrameTo(ctx, frameIndex, opts?): draws an arbitrary
recorded frame to an arbitrary ctx/position/size — "nearly free" since
frames are already rasterized bitmaps.seek()now uses this internally;
it's also the primitive behind section-overview thumbnails.src/studio/timeline.ts: shared time/frame↔pixel mapping
(timeToPixel/pixelToTime/frameToPixel/pixelToFrame) plus
computeSectionThumbnails()/renderSectionOverview()(a jump-to-section
overview strip) andcomputeStepMarkers(). Each render function has a
DOM-free "compute layout" half, independently unit-testable.MovingCameraScene.defineCameraStop(name, stop)/
goToCameraStop(name, config?): named camera viewpoints
(center/width/height/zoom), sugar overcamera.frame.animate.moveTo()/ setWidth()/setHeight(), applied as a single composed animation (not one
per field, which would otherwise race to overwrite the frame mobject's
points each tick).zoomscales the frame's own width/height — documented
as a distinct concept from the interactive camera'scamera.zoom
multiplier.copyMemberwiseStyle(dest, src, extraExclude?)(src/mobject/copy_style.ts):
a shared denylist-based memberwise style copy, extracted from
Mobject.become(). Now also used byalwaysRedraw()andreactive()'s
rebuild step, in place of their own independently-hardcoded allowlists —
any current or future custom field on aMobjectsubclass now redraws
correctly through all three paths, not just the fields each one happened
to enumerate.KeyframeTrack<T>/PlayKeyframeTrack/animateSignal()
(src/animation/keyframe_track.ts): a unified keyframe-track primitive
that, unlike every other easing tool here, keeps its structured, mutable
keyframe list around for introspection/editing (a Studio scrub UI can
splice keyframes directly;valueAt(t)reflects it immediately).
Per-keyframeease(aRateFuncor a string resolved viarunning())
eases the transition arriving at that keyframe. Default interpolation
handlesnumber/number[]viaV.lerp;options.interpolateis the
escape hatch for other types (e.g.Color.lerpfor a color track).
PlayKeyframeTrackis anAnimationforscene.play()-driven use
(explicitconfig.runTimewins over the track's own duration, same
precedence astransitions.ts'sspringTiming());.valueAt(t)is also
usable directly inside a plainaddUpdater.animateSignal(signal, track)
points aPlayKeyframeTrackat a signal's setter, giving "a signal driven
by a keyframe timeline" with no separate mechanism.SceneRendererinterface +renderFrame()(src/renderer/scene_renderer.ts):
CanvasRenderer,ThreeRenderer, andSVGRenderereach gain an additive
renderFrame(mobjects)method that purely delegates to their existing,
differently-named public method (renderScene/render/renderToString
respectively). Those existing methods are unchanged and remain the
primary API (used across 15+ call sites) —renderFrame()is a shared,
uniform entry point for code that wants to treat any backend
interchangeably, not a replacement or rename.reprojectCurve(domainSamples | curve, targetSystem, options?)
(src/mobject/coordinate_reprojection.ts): rebuilds a curve sampled in
domain (coordinate) space against a different coordinate system (e.g. an
Axes-plotted curve reprojected onto aPolarPlane), reusing the same
setPointsAsCornersconstructionAxes.plot()uses so fidelity matches a
curve plotted directly against the target.targetSystemis typed
structurally ({ coordsToPoint(a, b) }), so `Ax...
ecmanim 0.0.10
Fixed
- Node:
Text/estimateTextSize()now auto-load a system font lazily on first use, instead of silently falling back to the raster/CHAR_ASPECTestimate until an explicitloadVectorFont()call (issue #16).
ecmanim 0.0.9
Fixed
loadVectorFont/resolveFontPath(Node) are now exported fromecmanim/ecmanim/node, not just the internalrenderer/fonts-node.tsmodule (issue #14, PR #15). Without a public way to force the vector-glyph font pathrender()uses internally,Text/estimateTextSize()measured before the firstrender()call in a process silently used the raster/CHAR_ASPECTestimate instead, which could disagree with the real glyph metricsrender()ends up using by ~10% — enough to turn a correctly-gated "measure before you render" layout check into clipped output.
See CHANGELOG.md for full details.
ecmanim 0.0.8
Changed
skills/ecmanim-practical-authoring: removed the "Confirmed library
gotchas" section documenting issues #3, #5, and #7 — all three are fixed
and published as of 0.0.7, so those write-ups and workarounds were stale.
Replaced with a short "Reporting a new bug" pointer to
assets/bug_report_template.md.
See CHANGELOG.md for full details.
ecmanim 0.0.7
Fixed
FadeIn(and other single-mobjectAnimationsubclasses) silently duck-typed an extra positionalMobjectargument asconfig, corrupting the first mobject instead of raising an error. Fixes #7. TheAnimationbase constructor now throws aTypeErrorwhenconfigis mobject-shaped; multiple mobjects must be wrapped in aGroup.Mobject.colorwas a dead field for rendering purposes — raw assignment never syncedVMobject'sstrokeColor/fillColor, which the renderer actually reads. Fixes #5.coloris now a getter/setter backed by_color, forwarding tosetColor().- CLI
render/plancrashed withTypeError: Class constructor ... cannot be invoked without 'new'for scene files that importedScenethrough a different specifier than the CLI itself. Fixes #3 (also closed #4 as a duplicate). Replacedinstanceof Scenechecks with a duck-typedisSceneLike().
See CHANGELOG.md for full details.
ecmanim 0.0.5
Fixed
Axes/NumberPlane'sc2p()didn't track transforms applied after construction (shift(),moveTo(), nesting inside a shifted parent, etc.), reported as #2. Fixed at the root:NumberLine.numberToPoint()/pointToNumber()now derive from the axis line's current rendered geometry rather than frozen construction-time scalars, andAxes.coordsToPoint()/pointToCoords()were simplified to mirror upstream Python manim'sCoordinateSystem.coords_to_point.
See CHANGELOG.md for full details.
ecmanim 0.0.4
Fixed
Axes/NumberPlaneplaced points at the wrong x-coordinate for anyxRangenot centered on zero (e.g.[0, 70]) — fixes #1. See CHANGELOG for the full root-cause writeup.
ecmanim 0.0.3
Added
- A hierarchical
skills/folder (rootecmanimskill + 9 domain skills: timeline, captions/audio, voiceover, presentation, interchange, physics, authoring-pipeline, studio, render-cli) for LLM coding agents. Plain reference material — nothing auto-activates on install.
Fixed
skills/was never actually published to npm (present in git, missing from package.json'sfiles). Now included.skills/README.mddocumentsnpx skills-npmas the standard opt-in way to symlink these skills into an agent's skills directory.