A stack manager that deploys from its own directory resolved ./data to an
empty folder, so Postgres initialised a blank database and the install
looked wiped. The compose defaults are absolute now, and survive a sync
that deletes an untracked .env.
The backup script asks the running container where /app/uploads comes from
rather than trusting a setting, refuses to mirror an empty folder, and
won't write a dump of an empty library over backups that have one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The backup script missed DATA_DIR when the line carried an export prefix,
quotes, padding or a CR, and silently fell back to ./data — dumping first
and only then discovering there was nothing to mirror. It now strips all
that, prints the paths it's using, and stops before writing anything if
the files folder isn't there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A dated pg_dump plus an rsync mirror of the model files, with the stack
running. The dump is read back with pg_restore -l inside the container
before it replaces the day's file, so a bad run leaves older backups
alone. Older dumps are pruned after KEEP_DAYS.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DATA_DIR takes the files and PGDATA_DIR the database, so the database can
stay on a filesystem that supports ownership when the files live on a disk
that doesn't. The example .env points both at /Volumes/Data/printhive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The database and files now live under ./data (DATA_DIR to move them), so
they can be seen, backed up and restored without going through Docker.
tools/volumes-to-folders.sh copies an existing install's named volumes
across; it only copies, leaving the volumes in place.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Settings → Slicer: pick Bambu Studio, OrcaSlicer, PrusaSlicer, Cura, a
custom open-link template, or plain download (remembered per browser)
- Bambu Studio / OrcaSlicer launch via their URL schemes with a 10-minute
signed download link (/dl/<token>/<name>) that needs no session cookie
- PrusaSlicer and Cura fall back to a download (they can't open our links)
- 3MF projects report which app saved them; opening one in a different
slicer asks first
- "slicer" action on every file row and archive entry
- Drag model cards (file, or zip of all files) and file/entry rows out to
the desktop via Chromium's DownloadURL
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- The background worker now also gives models without a thumbnail one:
an embedded 3MF/plate preview, else a picture uploaded with the model,
else a 3MF preview or picture inside one of its archives, else a render
of an STL/OBJ/3MF (loose or inside an archive).
- tools/render_thumbnail.py renders without a GPU: meshes are simplified
per distinct part (the 8M-face lion project renders in ~8 s / ~200 MB),
3MF projects show their first plate in filament colours, and only
camera-facing faces are painted (inside-out meshes and mirrored parts are
re-oriented first).
- models.thumbnail_status stops retries when there's nothing to use; adding
files clears it. The browser now only renders FBX/STEP/3DM thumbnails.
- The library checks back while fingerprints or thumbnails are pending and
refreshes when they land.
- Python sidecar needs pillow and fast-simplification; compose keeps the
archive extraction cache in a volume. README covers the phase 2 features.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- threemf_stream reads Bambu Studio / OrcaSlicer project metadata: plates
(name, objects, preview images), filaments, and each part's type and
extruder. Placements now record which build item and part they belong to.
- convert_3mf.py leaves out negative/modifier/support parts, colours each
part by its filament (a part's extruder 0 falls back to its object's), and
takes --plate N. Geometry is still stored once per (part, colour).
- New threemf_info.py lists plates and filaments without loading geometry
and extracts plate preview images; served via /api/{files,entries}/:id/project.
- /glb?plate=N builds and caches a per-plate preview. Cached GLB names carry
a converter version, so previews made before colours are rebuilt.
- Model detail: plate chips (with the slicer's plate images) under the
viewer — multi-plate projects open on plate 1 — plus "All plates", and a
Filaments section with colour swatches.
- Fingerprints leave out negative/modifier parts, so a project matches the
model it prints.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
trimesh's 3MF loader re-reads a whole production-extension part file for
every component that points into it. A Bambu Studio project with 884
components across four ~50 MB part files grew past 3 GB within seconds
and failed both the preview conversion and the geometry fingerprint.
- Add tools/threemf_stream.py: parses each part once with lxml iterparse,
resolves each component to the single object it names, and returns each
mesh once plus its placements. The same project now converts in ~8 s at
~380 MB peak (fingerprinting ~190 MB).
- convert_3mf.py and fingerprint.py use it; the GLB stores each object once
and instances repeats. Stats are computed per placement (mirrored
placements keep a positive volume), without concatenating meshes.
- Run sidecar scripts through one queue (lib/python.js) so a preview and
background fingerprinting never load big meshes at the same time;
previews go first, and 3MFs are fingerprinted one per run.
- Surface the converter's JSON error / timeout instead of "Command failed".
- Tests with a Bambu-style component fixture (shared part, mirrored
placement, decoy object) and STL/3MF fingerprint parity.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Fingerprint every file in the background: SHA-256 for all formats plus a
trimesh geometry fingerprint for STL/OBJ/3MF, so renamed, re-exported or
converted copies are still matched. Backfills existing files on startup.
- Duplicates view groups models that share files (exact copies vs same
shape), recommends which to keep, merges tags/collections/notes into the
kept model and trashes the rest; groups can be dismissed.
- Deleting now moves models to a restorable trash; files are only removed
from disk on permanent delete or when the trash is emptied.
- Per-model notes with autosave.
- Search across names, notes, tags, files and designers; filter by format,
source and license; include/exclude tags with any/all matching; sort and
group by source or format. Preferences persist per browser.
- Add node:test unit tests for duplicate grouping and fingerprints.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Dockerfile: node:22-bookworm-slim + Python venv with trimesh for the
3MF->GLB sidecar (PYTHON_BIN env points the converter at it)
- docker-compose.yml: app + postgres 16 with healthcheck/depends_on,
named volumes for pgdata/uploads/thumbnails/glb, token env passthrough
- .dockerignore, tools/requirements.txt
- converter.js honours PYTHON_BIN (defaults to local venv)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>