Releases: Flux-Frontiers/quiltwright
Release list
Quiltwright v0.11.0
Release Notes -- v0.11.0
Released: 2026-08-31
This release closes the loop between a rendered scene and where people
actually see it every day: the desktop. quiltwright dynamic turns a set of
stills into a real macOS Dynamic Desktop file, and POV-Ray scenes gain the
day/night lighting to make that worth doing without hand-rolling clock
tricks. bj_holo_2026.pov is the first scene fully framed and lit for it.
Underneath, a duck-typed lens contract becomes a real protocol, and the
three rendering backends stop repeating the same handful of helpers.
What changed
quiltwright dynamic packs stills into a macOS Dynamic Desktop HEIC.
It writes the same appearance (light/dark), solar (altitude/azimuth), and
time-of-day (h24) metadata Apple ships in The Lake.heic and
Sonoma.heic, onto image 0 of the packed file -- so a woven _native_
Looking Glass quilt or an ordinary 2-D still can drive macOS's own
light/dark wallpaper cycle. Woven frames encode lossless 4:4:4 so the
hologram survives the trip; ordinary stills may be lossy. quiltwright wallpaper already installs the result by serial, same as it does a PNG.
Encoding lives behind the new heic extra (pillow-heif).
POV-Ray scenes get lighting= and sun= instead of hand-rolled clock
tricks. clock is POV-Ray's animation parameter (+K) and has nothing
to do with wall-clock time, so Dynamic Desktop lighting needed its own
knobs. lighting= and sun= on render_pov_quilt and render_pov_views
add them: lighting="light" leaves a scene's own lights alone, since an
additive key would wash the plate out; lighting="dark" appends a cool
moon and a short fog so Dark Mode still reads as night against an authored
white key; sun=(altitude, azimuth) sets an explicit parallel sun for
solar frames. A scene opts in through the QW_Appearance /
QW_SunAltitude / QW_SunAzimuth declares.
bj_holo_2026.pov is framed and lit for the desktop. The bell jar's
glass Y scale drops from 10.5 to 9.5 (the DNA inside is untouched) so the
dome leaves a sky band above it, and the title baseline moves from
y=62.45 to y=61.30 -- low enough that a macOS menu bar doesn't clip the
caps, high enough that the letters still clear the glass. Paired with
lighting="light"|"dark", it's the normal plate by day and a fog night
after dark, on both a Mac and a woven Looking Glass HEIC.
HasLens replaces the private lens protocol. depth_budget and
format_depth_budget read a narrow fov / focal_distance surface;
HasLens, lazy-exported from the package root, makes that contract public
so anything shaped like a lens -- every QuiltCamera, or a tiny namespace
like lfd's _Lens -- satisfies it without a special case. PovCamera.aimed
and CyclesCamera.aimed now share a parity suite in tests/test_camera.py
so that documented twin contract can't drift quietly.
Shared helpers move into quiltwright.runtime. require_pyvista
replaces three identical copies that had accumulated in lfd, hld, and
tvb_data; triple replaces the twin _triple helpers in povray and
cycles, imported there as _triple so call sites stay private.
Upgrading
No breaking changes -- pip install -U quiltwright is enough. Packing a
Dynamic Desktop HEIC needs the new heic extra: pip install "quiltwright[heic]".
Full changelog: CHANGELOG.md
Quiltwright v0.10.1
Release Notes -- v0.10.1
Released: 2026-08-31
This release moves house and tightens up what was already there. The
repository itself is now under the Flux-Frontiers org rather than a personal
account, and the documentation finally gets a home of its own: the guides
under docs/ build into a real site, an architecture page describes the
package as it stands rather than as a plan for getting there, and every CLI
command has a full flag reference for the first time. Alongside that, the
bell jar still life gets glass that actually refracts, a hologram-printer
sweep is now something you run rather than something you script by hand, and
a lens bug in the vitrine exhibits that had been quietly overstating headroom
is fixed.
What changed
Documentation now builds into a GitHub Pages site. docs/*.md renders
through MkDocs Material into
flux-frontiers.github.io/quiltwright,
alongside an API reference generated by mkdocstrings straight from the
package's own docstrings -- ten modules (quilt, lfd, povray, cycles,
povgen, hld, weave, bridge, tvb_data, runtime), so the reference
can't drift from what the code actually does. A GitHub Actions workflow
builds and deploys it on every push to main. Every doc link that used to
point at scripts/, pov-scenes/, or the top-level README as a path
relative to docs/ -- fine to read in the repository, but with no target
inside a site that only serves docs/ -- now points at an absolute GitHub
URL instead.
docs/architecture.md replaces docs/architecture-plan.md. The module
split the plan proposed had fully landed some time ago, which left a PR
proposal sitting in the documentation table long after it stopped being one.
The new page describes the package as it is: the module map, the acyclic
dependency graph between quilt, bridge, runtime and the three
backends, and the off-axis invariant (window_shear) that all three convert
into their own units.
docs/cli.md documents every quiltwright command, flag by flag.
docs/shell.md keeps the narrative of what each command is for and why, and
now links out to the new reference for the option tables instead of
duplicating them.
The repository moved to Flux-Frontiers. Every reference to the old
suchanek/quiltwright path -- README badges, the pyproject.toml project
URLs, CITATION.cff, source-file docstring headers -- now points at
Flux-Frontiers/quiltwright, matching where the rest of the fleet already
lives. The Zenodo DOI and the PyPI trusted-publisher configuration were
updated to match on the hosting side.
bj_holo_2026.pov gives "DNA Under Glass" a bell jar that refracts
instead of just tinting. The 1996 jar carried no interior, so its index
of refraction was 1.0 and it bent nothing; once it gained a real wall
thickness, that no-refraction surface applied twice at every edge-on
silhouette and drew a double outline instead of one glass rim. The new cut
turns on ior 1.52 with a Fresnel reflection, and the doubled outline
resolves into a single bright band, with the horizon bending correctly where
it crosses the wall. Dispersion was tried and dropped -- 4.5x the render for
a difference lenticular crosstalk erases anyway.
A LitiHolo printer sweep is now a first-class render, not a hand-rolled
script. --sweep on scripts/render_still_life_hologram.py swaps in the
23-view, 45-degree, single-row device spec LITIHOLO_SWEEP and otherwise
runs the same budget/render/save/report path a quilt does, so a sweep now
carries the same provenance a quilt always has.
A vitrine lens bug is fixed. scripts/render_vitrine.py was converting
its vertical field of view to POV-Ray's horizontal angle before handing it
to PovCamera, whose fov is vertical -- so every vitrine quilt rendered at
a 71.4-degree lens instead of the 44 degrees the scene is actually composed
at, and the depth budget printed alongside it understated adjacent-view
disparity by nearly half. The script now passes the vertical FOV straight
through.
Housekeeping. The three committed landscape quilts (bell-jar, museum,
porin) are release assets now rather than repository content, saving every
clone 97 MB of history it was paying regardless of whether it wanted the
renders. tests.yml no longer runs its full matrix twice per push to a
branch with an open PR.
Upgrading
Nothing to migrate. This release changes no public API -- pip install -U quiltwright is enough. If your bookmarks or scripts reference
github.com/suchanek/quiltwright, GitHub redirects them, but updating to
Flux-Frontiers/quiltwright avoids relying on that indefinitely.
Full changelog: CHANGELOG.md
Quiltwright v0.10.0
Release Notes -- v0.10.0
Released: 2026-08-30
Nothing in this release changes what comes out of the renderer. It changes the
shape of what produces it. Three backends had accumulated inside a package
whose geometry, device presets, Bridge client and PyVista rendering all lived
in one module, lfd.py, which meant that casting a finished quilt to a panel
imported VTK, and that the same off-axis window shift was derived
independently in three places. This release splits that module along the lines
it had already grown, and promotes the last few things that were reachable
only by copying code out of a script in a checkout.
The other half of the release is about arbitrary input. Until now every worked
example built its own geometry and therefore already knew where the camera
went. A mesh that arrives from somewhere else -- a modeling tool, a scan, an
asset library, a generator -- carries no camera and no known scale, and a
scene whose depth range has never been measured cannot have its budget
computed. Two new CLI commands close both gaps.
What changed
Quilt geometry and the Bridge client left the PyVista backend.
QuiltSpec, the device presets, assemble_quilt, save_quilt,
view_offsets, view_disparity, focal_distance_for_range and
sweep_extent now live in quiltwright.quilt, which needs only numpy and
pillow; cast_quilt and the transport controls live in quiltwright.bridge,
which is stdlib alone. quiltwright.lfd is what its name always implied -- the
PyVista backend -- and re-exports every moved name, so from quiltwright.lfd import QuiltSpec still works and nothing downstream has to move. Package-level
lazy imports resolve QuiltSpec without loading VTK at all, which is the point:
a core install can now compute a quilt's geometry and drive a panel without a
rendering stack anywhere on the machine. find_ffmpeg moved to
quiltwright.runtime for the same reason, since importing it used to drag
PyVista in behind it.
QuiltCamera and window_shear() state the off-axis shift once. VTK's
SetWindowCenter, Blender's shift_x and POV-Ray's direction shear are
three unit conversions of a single dimensionless number, and each backend used
to arrive at it separately. The shared form lives in quiltwright.quilt, with
the per-backend conversion left where it belongs. The protocol is named
QuiltCamera rather than Camera so it does not collide with layer 1's
CameraFrame, and lfd.camera_frame is now the public name for the vtkCamera
decomposition it was doing privately.
quiltwright mesh renders any 3D object file, with the camera derived from
the geometry. The Cycles backend could already import glTF/GLB, OBJ, STL,
PLY, USD, FBX and Alembic, but choosing a CyclesCamera for an unfamiliar
mesh was guesswork about scale, origin and post-import up-axis. mesh_bounds()
imports the file once, through the same importer the render uses, and reports
its world-space bounding box; frame_camera() turns that box into a camera
that fills the field of view via the exact spherical relation
sin(fov/2) = r/d and aims at the bounds center, which becomes the focal
plane; autoframe_camera() composes the two. frame_camera() is pure
arithmetic and unit-tested directly, and the end-to-end path is covered against
a real Blender import. The CLI command wraps all of it with --lighting,
--view-direction, --fov, --margin, --device, --samples, --still
and the rest. A .blend is refused with its reason, since it carries its own
camera and there is nothing to auto-frame.
quiltwright probe promotes the plane sweep into the package.
depth_sweep() and summarise_depth_sweep() are the measurement every near
and far figure in this repository was taken with, and until now they were
reachable only by running a script out of a checkout. They live in
quiltwright.povray beside the budget they feed, both tested -- the marker
plane's arithmetic and the summary thresholds directly, the sweep end to end
against a scene with known depths -- and both now take the same courtesy thread
cap the quilt renderers do, rather than every core for the length of a few
hundred frames. The command adds one thing the script never did: when a sweep
never closes, because the scene runs to a sea or a sky or any backdrop at the
horizon, the reported far value is the end of the sweep rather than a
measurement, and it now says so instead of letting that number be copied into
a scene.
Three smaller promotions out of the scripts. frame_and_focus() in
quiltwright.lfd is the PyVista counterpart to frame_camera(): it re-fits
the camera at the final view direction by projecting the bounding box's corners
onto the camera's own axes, so an obliquely viewed flat subject is no longer
held at its bounding sphere's distance, which is what used to make mountains
read as specks. fov_vertical_to_horizontal() in quiltwright.povgen is the
inverse of a conversion that was already public in one direction only.
QuiltSpec.still() gives a one-tile spec at the device's own aspect, replacing
the hard-coded 880x1100 literal each worked example carried, which ignored
--device and framed a landscape panel's still in portrait.
The CLI is hardware and tooling; scripts/ is the gallery. There is
deliberately no generic quiltwright render. cast, weave, wallpaper and
bridge operate on a finished quilt; mesh, cartoon and probe take
arbitrary input. The composed exhibits for the scenes this repository ships --
museum, vitrine, still-life, DNA helix, cartoon comparison -- stay in
scripts/, where they are the presented work rather than unfinished CLI. Two
private cross-module imports that this boundary exposed are now public
(bridge_post, enter_orchestration), which a fan-in of six and five had
already made them in practice.
git push no longer waits on the ray tracer. The pre-push pytest hook runs
-m "not slow", taking the local gate from 96 s to 16 s while still running
557 of 588 tests. The marker existed but carried only 13 tests: the three
classes in tests/test_povray.py that shell out to a real povray binary were
the bulk of the cost and were unmarked. They are marked now. Separately,
TestDepthSweep ray-traced its scene once per test because its fixtures were
function-scoped; class-scoping them takes 12 s off every full run, CI included.
Nothing is skipped rather than deferred -- CI runs the suite unfiltered on both
interpreters, and now runs on develop as well as main, which previously got
no CI at all.
Knowledge-graph tooling, pinned to the repository rather than the machine.
An optional kg Poetry group installs pycode-kg and doc-kg into
.venv/bin, and .grok/config.toml points both MCP servers at those paths, so
an agent working here talks to the pinned versions instead of whatever a global
install has drifted to. A group and not an extra on purpose: an extra reaches
the published wheel metadata and would hand every pip install quiltwright a
torch and sentence-transformers install for tooling the package never runs.
The docs document the Poetry path, and use US spellings. README.md and
docs/install.md now give the Poetry commands beside the pip ones -- every
extra has a dependency group of the same name -- and the pyproject.toml
header lists every working combination, including the comma-with-no-space form
Poetry requires. British spellings are gone from the prose across docs/ and
the READMEs; identifiers keep theirs, since they name real code.
Upgrading
Nothing to migrate. Every name that moved is re-exported from where it used to
live, so existing imports keep working; the only reason to update them is to
avoid loading PyVista for geometry that no longer needs it. pip install -U quiltwright is enough. The two new CLI commands need the backends they drive:
quiltwright mesh needs a blender binary, quiltwright probe a povray
one, the same as the libraries behind them.
Full changelog: CHANGELOG.md
Quiltwright v0.8.0
Release Notes -- v0.8.0
Released: 2026-08-24
Quiltwright's molecular exhibits had one composition: an object against an
empty sky. This release gives them a room, and gives the room a second way to
draw a molecule at all. A standard museum vitrine now hosts anything pdb2pov
or PyMOL can produce -- atoms, bonds, or a full Richardson cartoon -- on one
camera and one depth budget, with no per-structure tuning. Alongside it, the
molecules extra resolves straight from PyPI, and a single command runs the
whole pipeline from a PDB ID to a rendered quilt.
What changed
A room, not a sky. scripts/render_vitrine.py normalises any molecule to
a unit sphere using the enclosing radius pdb2pov already writes into every
file, and builds a stone plinth, a bell jar, and an alcove around that one
number. GFP (31.2 A), hemoglobin (40.3 A), OmpF (51.0 A) and F1-ATPase (79.0
A) -- a 2.5x range of molecular radius -- share one camera and one depth
budget, with no per-structure tuning, because the jar and the plinth taper are
both derived from the same enclosing sphere and cannot clip a structure it
describes correctly. A 2026 cut composed for a 16:9 panel joins the original
1999 room, and gallery/ moves to the repository top level -- it is
presented work, not a build artefact, and it looked like one buried under
renders/, which otherwise holds only output.
Richardson cartoons, forty years late. pdb2pov has emitted atoms and
bonds since 1993 and nothing else; the archive's one ribbon-cartoon image
survived from a 1993-94 mesh exporter whose output exists in no repository
anywhere. quiltwright cartoon closes that gap: cartoon_inc() writes an
object-only include on the same contract pypdb2pov writes -- origin-centred,
enclosing radius alongside it, no camera, no lights -- so the vitrine and the
rest of the render pipeline need no changes at all to mount one. Getting there
needed a mesh2 primitive povgen never had, built to honour the flip-z
winding contract explicitly (get the triangle orientation wrong and POV-Ray
lights a mesh from behind, which reads as a lighting bug for an hour), and a
coalescer for PyMOL's cmd.get_povray(), which writes one mesh2 per
triangle: OmpF arrives as 75,792 separate meshes and 41 MB, and leaves as one
mesh POV-Ray parses in a fraction of the time.
One command, start to finish. scripts/make_exhibit.py fetches a
structure, converts it, composes the scene, renders it, and sweeps the depth
budget -- python scripts/make_exhibit.py 7AHL --label "ALPHA-HEMOLYSIN" --quilt is the whole pipeline. Structures land in $PDB (default ~/pdb),
the convention proteusPy already follows, so a file fetched for one tool is
there for the next. It defaults to the biological assembly rather than the
deposited asymmetric unit, because nothing in a PDB file says the asymmetric
unit can be a fraction of the molecule -- ferritin's is a 24th of a ferritin.
The molecules extra is real. pip install "quiltwright[molecules]" now
resolves straight from PyPI, floored at pypdb2pov>=0.1.1 because 0.1.0's
published metadata mislabels the licence as GPL -- the project is actually
BSD-3-Clause, and PyPI metadata is immutable per release, so refusing to
resolve to 0.1.0 is the only way not to hand someone the mislabelled
version. render_vitrine.py now asks the installed package for its include
directory instead of a path that was only ever true on the machine it was
written on.
Upgrading
Nothing to migrate. pip install -U quiltwright or
pip install -U "quiltwright[molecules]" for the molecular exhibits, and
quiltwright cartoon --help covers the new command.
Full changelog: CHANGELOG.md
Quiltwright v0.7.0
Release Notes -- v0.7.0
Released: 2026-08-18
Quiltwright could render a quilt and hand it to Bridge; everything after that
was manual. This release closes that gap. Three new CLI commands cover the
whole tail of the pipeline -- putting a quilt on the panel, hanging a woven
frame on the desktop, and telling you which of those two is broken when the
glass goes black. Alongside them, every full quilt now writes a provenance
record, because a quilt is a 25-40 MB PNG that says nothing about where it
came from.
What changed
The CLI reaches the panel. quiltwright cast shows a saved quilt on the
display, recovering the tiling and aspect from the _qs<cols>x<rows>a<aspect>
filename suffix, so it usually needs no flags at all. It sits downstream of the
assembler, which means it does not care whether the views came from a PyVista
scene or a POV-Ray one. cast --check lists what Bridge can actually see and
marks which heads are Looking Glass panels rather than ordinary monitors --
Bridge enumerates both, and a cast landing on a laptop screen fails silently.
A hologram you can leave on the desktop. quiltwright wallpaper completes
the path that needs no Looking Glass software running at all. A woven frame is
already interleaved for one specific panel, so displaying it 1:1 is the
hologram, and the simplest thing that displays an image 1:1 forever is the
desktop picture. The command matches frame to display by the panel serial both
carry, installs it somewhere stable first -- macOS stores wallpaper as a path,
so pointing the desktop into renders/ blanks the panel the next time that
directory is cleaned -- and refuses to guess when no display matches, because a
woven frame on the wrong panel is a screenful of noise.
Bridge, which fails dishonestly. quiltwright bridge status and reset
exist because Bridge keeps its HTTP port open and keeps issuing valid
orchestration tokens after it has crashed internally. A cast then reports
success at every step against a daemon that will never draw a pixel, and the
only symptom is a black panel. status therefore distrusts a bare 200: it
walks the port, the session, the device enumeration, and whether any device is
a panel at all, then gives a verdict and a matching exit code so it can gate a
cast in a script. reset kills every Bridge process and relaunches, which is
the only reliable fix -- Bridge's own menu restart spawns a replacement that
inherits the wedge.
Run reports. Every full quilt from make now writes a Markdown provenance
record to renders/reports/. Quilts are release assets and are not committed,
so without this there is nothing in the repository saying how one was made. The
report carries the scene file and its SHA-256 -- composing a scene means
rendering against an edited working copy, so the commit alone can describe a
tree the render never saw -- along with the camera and its measured depths, the
depth budget verbatim as printed, the parallelism actually used, timings, and
the output's own digest.
A render outside make no longer takes the whole machine. POV-Ray threads
one render across every core it can see, and neither existing guard applied at
the documented jobs=1. So make quilt-museum held two cores back and calling
the same script directly did not. There is now a courtesy cap, and a
--threads flag to set or disable it. It yields to a Work_Threads line in
POVINI, because a command-line +WT overrides an INI outright and capping
unconditionally would have defeated make quilts RENDER_THREADS=$(nproc).
Housekeeping. The CLI is now a package of cmd_* modules matching the rest
of the fleet, which moved the console-script entry point. The README no longer
describes Quiltwright as the tail of two specific pipelines: it takes any
PyVista/VTK scene in memory and any POV-Ray scene on disk, and WaveRider and
pdb2pov are users rather than prerequisites.
Upgrading
Nothing to migrate. pip install -U quiltwright and the new commands are
there; quiltwright --help lists them.
Two things to know. If you imported quiltwright.cli directly -- an
undocumented path -- it is now quiltwright.cli.main; the quiltwright
console script is unaffected. And if you drive the render scripts yourself
rather than through make, they now hold two cores back by default; pass
--threads 0 to restore the old behaviour of taking every core.
When a cast leaves the panel black, start with quiltwright bridge status
rather than re-rendering.
Full changelog: CHANGELOG.md
Quiltwright v0.6.0
Release Notes — v0.6.0
Released: 2026-08-16
Quiltwright 0.6.0 closes the last gap between rendering a quilt and seeing it
on a panel: one call now writes the file and hands Looking Glass Bridge the
path to it, and a quilt can be scaled down for fast casting without breaking
the tiling that makes it a light field.
What changed
Save and cast in one call. save_quilt takes the pixel array and
cast_quilt takes a path, and confusing the two is invisible until a display
is connected — the caster's ndarray error arrives minutes into a ray-traced
render, at the worst possible moment. save_and_cast_quilt() composes the two
correctly: it confirms the file is on disk before contacting Bridge, and a
failed cast comes back as a (path, error) return rather than an exception,
so a Bridge that isn't running never costs you the render. Consumers had been
writing this wrapper by hand; now it ships in the box.
Scaling that keeps the tiling. Casting at full preset size is rarely worth
the wait — Bridge's load time scales with the PNG's area, so halving the
linear size quarters it. But scaling a quilt naively stops it dividing evenly
into the view grid, landing every view on a fractional pixel boundary and
smearing the light field. QuiltSpec.scaled(factor) rounds the new dimensions
down to a multiple of the tile grid so views stay pixel-aligned, and refuses
factors that would leave less than a pixel per tile.
Upgrading
Nothing to migrate: both additions are new API, exported from the package
root, and no existing behaviour changed. Replace any hand-rolled save-then-cast
helper with save_and_cast_quilt() and drop your own size arithmetic in
favour of spec.scaled(0.5).
Full changelog: CHANGELOG.md
Quiltwright v0.5.0
Release Notes — v0.5.0
Released: 2026-08-16
Quiltwright had two rendering backends that never met: lfd sweeps a live
pv.Plotter, povray sweeps a .pov file on disk. quiltwright.povgen
produces the second from the first, so a scene composed in Python — or grown
by a geometry engine such as kg_utils.viz3d — can be ray-traced instead of
rasterised by VTK.
What changed
Analytic, not a mesh dump
By the time geometry reaches a pv.Plotter it is already tessellated:
pv.Sphere is a triangulated ball. Dumping those triangles into a mesh2
keeps VTK's facets and costs a great deal of text, re-parsed once per view —
48 times for a Portrait quilt. povgen re-emits intent instead: a limb is a
sphere_sweep, a leaf is a sphere.
Measured on a 3000-leaf organic tree (192k triangles, 159k vertices once
tessellated):
| Form | Size |
|---|---|
mesh2 equivalent |
~12.5 MB |
| analytic, oriented leaf instances | 839 KB |
| analytic, plain spheres | 508 KB |
15× to 25× smaller, with exact silhouettes at any zoom — which is most of
the reason to leave VTK in the first place. mesh2 stays unimplemented, as
the fallback for geometry with no analytic description.
Verified by dual render
tests/test_povgen_parity.py renders the same scene through both backends at
a matched camera and compares silhouettes: IoU ≈ 0.95 with identical
bounding boxes. The fixture is deliberately asymmetric in depth, because a
scene straddling the focal plane renders almost identically whether or not z
was flipped — the first draft passed cleanly with the handedness conversion
removed entirely.
Four decisions made for the caller
- Handedness. Scenes are authored right-handed and
zis negated on
emission. Box corners are re-sorted, andInstancerotations are conjugated
by the reflection. - No camera is emitted.
render_pov_quiltappends one off-axis camera per
view and POV-Ray honours the last it parses. - Opacity becomes
transmit, notfilter.filterwould tint everything
seen through the surface. SphereSweepdefaults tolinear_spline, which interpolates its control
points rather than pulling away from already-smoothed geometry.
Three fixes from the first consumer
Building an analytic tree in gutenberg_kg on top of this surfaced three
things worth having before anyone else does the same:
-
lights_from_boundstakesup. Above was hard-coded to+y— right
for a VTK scene, wrong for the+z-up worldkg_utils.viz3dbuilds, where
the key light landed below the ground and lit the subject from underneath.
The default is unchanged, so no existing caller moves. -
povgenno longer drags in VTK. It is NumPy-only by design but could not
be imported without the rendering stack: the package__init__re-exported
lfdeagerly, andpovgenimportedpovrayforPovCamera. Both are
deferred now — the public API is identical. -
PovCameraholds POV-Ray coordinates, and now says so. Three places
implied otherwise. A consumer framed a camera in the scene's right-handed
world and got a flawless render of empty space, with every assertion passing
because they compared right-handed against right-handed.
PovScene.bounds() also now documents what instancing costs it, and how to
work around it.
Upgrading
Nothing breaks. lights_from_bounds keeps its +y-up default; pass
up=(0, 0, 1) for a +z-up scene. The lazy __init__ leaves every name in
__all__ reachable exactly as before.
Quiltwright v0.4.0
Release Notes — v0.4.0
Released: 2026-08-14
Two things a downstream project had to work around are now the library's job.
depth_report() measures a PyVista scene's depth budget directly, replacing a
forty-line helper that two separate consumers had each written for themselves —
and it models the framing render_quilt() actually uses, which those copies did
not. Separately, the <3.13 ceiling is gone: quiltwright runs on Python 3.13,
and the marker every consumer carried to work around that ceiling can be dropped.
What changed
The depth budget for a PyVista scene, without a throwaway camera.
format_depth_budget() has always done the arithmetic, but it takes a
PovCamera, so a PyVista caller had to measure the scene by hand and then build
a POV-Ray camera it would never render with, purely as a vehicle for a FOV and a
focal distance. depth_report(plotter, spec) reads the plotter's bounds and
camera and returns the report.
The reason it is worth having in the library rather than copied a third time is
the part the copies got wrong. render_quilt() narrows the FOV and dollies back
before it sweeps, so a budget measured from the plotter as-composed describes a
picture nobody is going to make — wrong FOV and wrong focal distance. On a
torus at the default framing the two answers differ by about 15%, and the sign
of that error depends on the scene, because dollying back partly offsets the
magnification. depth_report() takes the fov and zoom you intend to pass to
render_quilt() and models both.
scene_depths() exposes the measurement alone, for callers that want numbers
instead of a formatted report, and DEPTH_LABELS supplies domain-neutral
defaults — "nearest geometry" rather than any one field's vocabulary. Neither
function mutates the plotter.
Python 3.13 is supported. requires-python is now >=3.12,<3.14. The old
ceiling had no recorded rationale and no dependency behind it — numpy, pillow and
pyvista all support 3.13 — but it propagated outward: any project allowing
<3.14 had to declare quiltwright marker-gated, because an unmarked declaration
made Poetry reject the entire resolution. CI now runs the suite on both 3.12 and
3.13, so the classifier is a tested claim rather than an assertion.
Upgrading
Nothing breaks. depth_report(), scene_depths() and DEPTH_LABELS are new
exports; every existing signature and rendered pixel is unchanged.
If you carry the marker-gated declaration — "quiltwright>=0.3.1; python_version < '3.13'" — you can now drop the marker and pin plainly:
quiltwright = ">=0.4.0"If you hand-rolled a depth budget for a PyVista scene, replace it with
depth_report(plotter, spec, fov=..., zoom=...) and pass the same fov and
zoom you give render_quilt(). Expect the numbers to move — that is the fix,
not a regression.
Full changelog: CHANGELOG.md
Quiltwright v0.3.1
Release Notes — v0.3.1
Released: 2026-08-10
A documentation release. The view sweep that feeds a hologram printer has been
in the package since 0.2.0, but the README treated it as an aside — one
paragraph tucked in after two display technologies had already had their say.
It is now a third output: a branch in the pipeline diagram, a section in the
quick start, and a status said out loud rather than implied. In development.
What changed
The LitiHolo path reads as an output. The diagram gained a branch for it
and a view sweeps line in the middle box, and the column it sits in is
labelled "outputs" rather than "displays", because a printer is not a display.
A new Send it to a hologram printer section runs LITIHOLO_SWEEP through
render_pov_views() end to end — why 23 views cannot be a quilt grid, where
the frames land, and what the sweep is and is not.
What it is not travels with it, in each place a reader might stop: quiltwright
emits a sweep matching LitiHolo's published specification, which is a narrower
claim than compatibility with the printer. Nothing has been through the
printer's software. Whether a hogel slicer wants off-axis frusta or a toe-in
arc is still open, and so is whether 2.05° between views is too coarse. This
path is POV-Ray only.
The worked example fails on purpose. It prints format_depth_budget()
before it renders anything, and the museum at a 45° cone reports ~43 px of
adjacent-view disparity against an ~8 px ghosting threshold. That is the report
doing its job — a number worth having before the ray-tracer starts, not after —
and it seemed more useful than an example framed to look effortless.
A consistency pass over the surrounding docs. LitiHolo is spelled with the
capital H the company uses, in all five places it appears. The coarse-sampling
ratio is 2.75×, not the 2.7× that four files had rounded it down to; 45° over
22 intervals against 35° over 47 is 2.747. render_pov_views()'s docstring had
asserted that hogels are "no more forgiving than a lens sheet" while all three
prose docs hold that question open, so it now hedges the way they do. The
README's scene-source paragraph finally mentions quiltwright.tvb_data, which
0.3.0 added without the README following, and docs/gallery.md — which nothing
in the repository linked to — joins the documentation table.
Upgrading
Nothing to do. No behaviour changed: one docstring was reworded, and every
signature, preset and rendered pixel is identical to 0.3.0. Upgrade only if you
want the version metadata to match.
Full changelog: CHANGELOG.md
Quiltwright v0.3.0
Release Notes — v0.3.0
Released: 2026-08-10
Quiltwright 0.3.0 adds a scene source. Until now the package took geometry you
already had — a POV-Ray scene, a PyVista mesh — and put it on a display.
quiltwright.tvb_data goes and fetches some: real cortical surfaces,
structural connectomes and parcellations from
The Virtual Brain, downloaded on demand
and handed over as ordinary meshes.
What changed
Brain geometry, as a scene source. quiltwright.tvb_data covers 11
surfaces, 8 connectomes, 4 parcellations and 9 sensor sets — human and
macaque — through load_surface(), load_connectivity(),
load_region_mapping() and load_sensors(), plus surface_polydata() and
connectome_polydata() for meshes that drop straight into a plotter. It sits
alongside the POV-Ray and PyVista paths as a source of geometry and says
nothing about how that geometry reaches a display.
This belongs here for the same reason scripts/render_pyvista_hologram.py
already downloads the Allen Institute mouse atlas: choosing subjects with real
depth structure, and going and getting them, is something this package was
already doing. docs/pyvista-datasets.md was already the place that thinking
lived.
The data is fetched, never shipped. tvb-root contains no data of its
own; the datasets live in tvb-data, a 337 MB archive on Zenodo
(doi:10.5281/zenodo.10128131),
licensed GPL-3.0. It is downloaded on first use, MD5-verified, and cached.
Nothing is vendored into this BSD-3 tree. That is the same line the package
already draws around ffmpeg — a GPL artefact is something we fetch on request,
never something we ship — now applied to data as well as binaries.
No new dependency comes with it. Loading needs only the standard library and
NumPy, both already core; the PyVista bridge uses the existing viz extra.
Nothing in the module encodes video, so imageio-ffmpeg stays exactly where
it was, in the optional video group, and a test asserts the module source
never reaches for it.
One answer for where downloads go. quiltwright.cache gives every runtime
download the platform's own cache directory — ~/Library/Caches/quiltwright
on macOS, $XDG_CACHE_HOME/quiltwright on Linux, %LOCALAPPDATA% on Windows
— matching PyVista, which caches through pooch.os_cache and so has always
put its own downloads in ~/Library/Caches on a Mac. The Allen atlas
downloader had hard-coded ~/.cache/quiltwright/allen_ccf, which is only
native on Linux; it now shares the same root, honours
$QUILTWRIGHT_ALLEN_CACHE, and adopts an existing download at the old path
rather than silently re-fetching a volume that runs well over a gigabyte at
10 µm.
The archive's inconsistencies are absorbed, not passed on. tvb-data is
not uniformly formatted, and one of its quirks fails silently: cortex_2x120k
indexes triangles from 1 while every other surface indexes from 0, so a naive
load produces an index one past the last vertex — a corrupt mesh rather than
an error. That is detected and rebased, as are split hemispheres,
folder-nested members, indices written in float notation, bz2-compressed
members, and a 1-byte vertex-normals stub. Each has a test.
Decimating a parcellated surface re-assigns its region labels by nearest
neighbour, because quadric decimation discards point data and interpolating
between region 3 and region 70 would mean nothing.
Upgrading
Nothing breaks. This release is additive: no existing signature changed, and
the only behavioural change is where the Allen atlas caches, which migrates
itself.
If you keep large downloads on a separate volume, the new environment
variables are $QUILTWRIGHT_TVB_CACHE and $QUILTWRIGHT_ALLEN_CACHE.
Getting started
import pyvista as pv
from quiltwright import QUILT_PRESETS, render_quilt, save_quilt
from quiltwright.tvb_data import surface_polydata
cortex = surface_polydata("cortex_16384", region_mapping="regionMapping_16k_76")
p = pv.Plotter(off_screen=True)
p.add_mesh(cortex, scalars="region", cmap="turbo", show_scalar_bar=False)
spec = QUILT_PRESETS["portrait"]
save_quilt(render_quilt(p, spec), "cortex", spec)See docs/tvb-data.md for the dataset reference, cache
layout and licensing.