Sliced files previewed through a vendored copy of PrettyGCode in an
iframe. It drew each move as a screen-space line -- a line has no
thickness in the scene, so it cannot occlude the layer behind it, which
is why prints came out stringy and shimmered where layers crossed. Being
a separate app in a frame, it could be neither themed nor translated, and
carried its own machinery for detecting a proxy refusing the embed.
Now built on libvgcode, the renderer OrcaSlicer draws its own preview
with, vendored from three-slicer (AGPL, same as us). It takes the THREE
namespace as an argument and imports nothing, so it runs on our 0.181
rather than the 0.160 its package pins.
The parser is ours; upstream renders its own kernel's output and ships no
G-code parser at all. Two things it has to get right, both found by
checking a real plate rather than assuming:
- BambuStudio does not use the OrcaSlicer/PrusaSlicer annotations. It
writes "; FEATURE:", "; LINE_WIDTH:", "; CHANGE_LAYER" and
"; Z_HEIGHT:", not ";TYPE:", ";WIDTH:" and ";LAYER_CHANGE". Reading
only the latter showed a 52-layer print as 23,165 layers in one colour,
because with no layer marker recognised every travel Z-hop split a
layer and every segment took the fallback feature.
- It emits a tenth of its moves as G2/G3 arcs -- 706 extruding ones in a
single plate. Ignoring them punched holes through curved walls and tree
supports. Arcs with no X/Y are the helical travel lift and lay down
nothing, so they interpolate as travels.
Four colour modes: filament (default, from the AMS slots the file was
sliced with), feature, layer height, line width. Speed, fan and
temperature are deliberately absent -- upstream derives those from
settings rather than the toolpath, and guesses dressed as measurements
are worse than an honest omission. The parser now carries the data to do
them properly later.
Legend entries are switches. Hiding removes the records before the mesh
is built rather than recolouring them: the shader packs colour into a
single float with no alpha, so there is no transparent to set, and
removal is the useful behaviour anyway -- a hidden support stops
occluding what it covered.
The scene is built once and only the toolpath rebuilds. Doing otherwise
constructed a new WebGLRenderer on every render, because the buildVolume
default is an object literal and so a fresh identity each time; browsers
cap live WebGL contexts and drop the oldest, which blanked the canvas
after a few interactions.
utils/framing.ts goes with the iframe, along with six now-orphaned
strings in all 13 locales. src/lib/vendor is excluded from eslint --
acting on findings in vendored code makes it impossible to re-copy on the
next upstream release.
PR #1195 (d6a31393) set Vite's base: '' to emit relative asset URLs
in the built index.html, intended as a partial improvement for
path-prefixed reverse proxies. The relative URLs broke every deep
SPA route on initial load: popup windows, direct URL paste, page
refresh on /camera/<id>, /projects/<id>, /groups/<id>/edit,
/external/<id>, /files/trash, and the SpoolBuddy kiosk paths.
Browser resolves ./assets/index-XXX.js against the document URL.
For /camera/<id>, that gives /camera/assets/index-XXX.js — the SPA
catch-all returns index.html (text/html) for that path, and modern
browsers refuse to execute HTML as a JS module under
X-Content-Type-Options: nosniff. Hence the empty-popup symptom
reported on #1221 across P1S / P2S / X1 / Docker / git / Chrome /
Firefox / Brave / Safari, plus the quieter "blank page on refresh"
on every other deep route.
Reverts the two PR #1195 lines: removes base: '' from
vite.config.ts (Vite default '/' restored, emitting absolute asset
URLs /assets/..., /manifest.json, /sw-register.js) and reverts
register('sw.js') to register('/sw.js') in public/sw-register.js.
PR #1195's class of bug — path-prefixed reverse proxy users serving
Bambuddy at a subpath — was already explicitly closed as wontfix in
that thread because supporting it requires subpath-aware
bootstrapping (API_BASE, React Router basename, PWA manifest scope,
SW scope) for every user forever. The supported alternative for that
audience stays as documented in the #1195 closing comment: NPM
(Nginx Proxy Manager) addon + Cloudflare Tunnel at a real domain
with HTTPS, then HA Webpage panel embedding via
TRUSTED_FRAME_ORIGINS — that path doesn't depend on base: '' at all.
The trade-off is intentional: revert reaches every user impacted by
deep-route initial-load bugs (much larger population than
path-prefixed proxy users), in exchange for an already-wontfixed
subpath-proxy regression that has a working alternative.
Vite's default base of '/' baked absolute asset URLs into the built
index.html (/assets/..., /manifest.json, /img/..., /sw-register.js),
so any path-prefixed reverse proxy (Traefik, nginx subpath, Cloudflare
Tunnel with path routing) served the SPA as a blank white page — the
browser requested assets from the host root and got HTML or text/plain
back, triggering MIME-mismatch errors on every stylesheet/script.
Set base: '' in vite.config.ts so the HTML transform emits relative
URLs everywhere. Update public/sw-register.js to register('sw.js')
(relative) so SW scope auto-pins to whatever subpath the document
loaded from.
Out of scope: API_BASE in client.ts is still absolute. The supported
HA embedding path remains Webpage panel + TRUSTED_FRAME_ORIGINS, not
HA Ingress (subpath-aware SPA bootstrapping has too many failure modes
around PWA scope, push subscriptions, and deep-link reloads to take on
in core). Documented explicitly in docker.md.
Reported by @Spegeli, follow-up to #1167.
* feat: add embedded GCode viewer
Adds PrettyGCode as a built-in GCode visualiser embedded directly in the
Bambuddy layout, so users can preview and inspect GCode files without
leaving the dashboard.
Manually configure AMS slots for third-party or generic filaments:
1. Hover over an AMS slot on the printer card
2. Click the menu button (:material-dots-vertical:) that appears
3. Select **Configure Slot**
4. Choose a filament preset from your Bambu Studio cloud presets
5. Select a matching K profile (pressure advance calibration)
6. Optionally set a custom color using the color picker
7. Click **Configure Slot** to apply
**Color Picker Features:**
- Enter custom hex codes or color names (e.g., "brown", "FF8800")
- Live preview of selected color
- Expandable color picker in Configure AMS Slot modal:
- 8 basic colors shown by default
- 24 additional colors available via expand button
- Tests for ConfigureAmsSlotModal component
- Tests for AMS change callback
- Updated README with AMS slot configuration feature
- Wiki documentation for Configure AMS Slot feature
- Multi plate issue where plate names showed incorrect. #93
- Items from Queue end up as "source" files in archive. #107
- Added env variable support to change network port. #108
Docs -> https://wiki.bambuddy.cool/getting-started/docker/?h=port#custom-port
- Added DEBIAN_FRONTEND=noninteractive to suppress debconf warnings
- Added --root-user-action=ignore to pip install
- Added ffmpeg to Docker image
- Increased chunkSizeWarningLimit to 3000 for Vite build
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>