A guided model-to-print workflow in front of Bambuddy.
  • JavaScript 85.3%
  • CSS 8.1%
  • HTML 6.3%
  • Dockerfile 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-29 09:09:18 +00:00
.forgejo/workflows Move printit out of home-cloud into its own repo 2026-09-29 05:08:33 -04:00
k8s deploy: pin image sha-0c24e84 2026-09-29 09:09:18 +00:00
lib chore: move to @managoat/fountain-sdk (#222) 2026-09-10 08:50:42 -04:00
public apps: make PrintIt models from prompts and reference images (#192) 2026-09-07 01:03:28 -04:00
test chore: move to @managoat/fountain-sdk (#222) 2026-09-10 08:50:42 -04:00
.dockerignore Move printit out of home-cloud into its own repo 2026-09-29 05:08:33 -04:00
.gitignore apps: add PrintIt family model-to-print workflow (#188) 2026-09-06 23:42:41 -04:00
build.mjs apps: add PrintIt family model-to-print workflow (#188) 2026-09-06 23:42:41 -04:00
Dockerfile apps: add PrintIt family model-to-print workflow (#188) 2026-09-06 23:42:41 -04:00
package-lock.json chore: move to @managoat/fountain-sdk (#222) 2026-09-10 08:50:42 -04:00
package.json chore: move to @managoat/fountain-sdk (#222) 2026-09-10 08:50:42 -04:00
README.md Move printit out of home-cloud into its own repo 2026-09-29 05:08:33 -04:00
server.mjs apps: make PrintIt models from prompts and reference images (#192) 2026-09-07 01:03:28 -04:00

printit

A family app for adding a model, sizing it for the printer, reviewing a sliced preview, and starting a print through bambuddy. It is reachable only on the tailnet at https://printit.<tailnet>.ts.net.

Using PrintIt

The printer panel stays visible throughout the workflow. It shows availability, preparation, printing, pauses, progress, layers, and alerts, including jobs started outside this browser. Show live camera opens the printer's MJPEG feed through PrintIt. Hidden tabs release their camera connection and reconnect when reopened; closing a viewer does not stop other viewers. Stalled images are hidden and can be reconnected. The camera and printer remain behind the tailnet boundary.

  1. Drop an STL, OBJ, or 3MF (up to 20 MB / 150,000 triangles), paste a MakerWorld model URL or direct HTTPS download, or describe an object.
  2. Pick a printer, adjust the size or orientation, and choose a loaded PLA spool, finish, and installed plate. Oversized models shrink proportionally; small models stay at their original size. P1/X1 printers use a conservative 20 mm inset to clear their cutter exclusion; A1 models use 5 mm. STL and OBJ coordinates are mm; 3MF units and object transforms are honored.
  3. Prepare the preview. PrintIt sends the transformed STL, with explicit printer, process, filament, and plate presets, to Bambuddy's async slicer.
  4. Review the slicer's plate image or interactive extrusion layers, time and filament estimates. Confirm the bed is clear and filament is loaded, then start printing. Acceptance of the command is reported separately from the live printer state.

Describe → Make my model asks a modeling agent to create and compile a new parametric OpenSCAD design in a fresh Sprite through Fountain. Attach up to three PNG, JPEG, or WebP reference pictures; the browser resizes them before sending. Include measurements for fitted parts: pictures guide shape, not exact dimensions. The finished model includes assumptions and print notes, downloadable STL and editable CAD, and Describe a change to revise the source in a fresh workspace. The separate library search matches saved filenames/notes and links to MakerWorld. Use a sample tray remains available without a model-generation service.

The server downloads the compiled binary STL, source, and design metadata through Fountain's sandbox files API. It independently checks file completeness, finite geometry, closed and consistently oriented triangle edges, and positive volume. Invalid deliverables get up to two repair turns. These checks do not establish wall thickness, freedom from self-intersections, support requirements, or physical fit; inspect the model and sliced layers before printing. Generation never starts a print. The worker receives no Fountain callback token or printer credentials.

Design jobs survive reloads and server restarts. A lost start response is recovered by its saved conversation channel, without blindly starting another paid run. Each browser session can make eight designs, one at a time; the app permits two active designs in total, with a 15-minute timeout per design. Workers are terminated after artifact collection or failure. Accepted output is limited to 60,000 triangles and 4 MiB of STL, with 128 KiB of source. Images are limited to 2 MiB each and 4 MiB total after resizing. These are household limits, not per-person billing controls.

This first version prepares single-color PLA with a 0.4 mm nozzle. Supported bed families are P1S, P1P, X1C, A1, and A1 Mini; preset availability is validated at slice time. The household P1S is the live integration target. Unknown printers, missing profiles/materials, and already sliced G-code are rejected. 3MF project colors/settings are discarded; all printable build objects are flattened into one geometry with their relative positions preserved. Check assemblies before printing, or export the desired part as STL. This is not an automatic support-design, mesh-repair, or multi-plate/multicolor workflow.

App layout

path role
server.mjs HTTP API, session-scoped projects, slice polling, preview and print lifecycle
lib/geometry.mjs STL/OBJ/3MF parsing, unit/transform handling, fitting, template geometry
lib/printers.mjs printer and material allowlists, compatible preset selection, dispatch checks
lib/sources.mjs bounded downloads with public-address validation and DNS pinning
lib/designs.mjs durable Fountain jobs, image validation, artifact checks, repair and cleanup
lib/design-agent.mjs modeling agent instructions and Sprite environment definition
public/ responsive family UI and interactive Three.js bed preview
test/ geometry and lifecycle tests, simulated Bambuddy, local UI preview server
k8s/ Kubernetes manifests (Kustomize), applied by Flux from home-cloud
.forgejo/workflows/test.yml app tests and browser bundle build on PRs
.forgejo/workflows/build.yml arm64 container build into the Forgejo registry on main, then the image pin

Project lifetime and printing behavior

Projects and print receipts live under PRINTIT_DATA_DIR (/data on a 2 GiB Longhorn volume in the cluster; /tmp/printit by default for local development) with a random HttpOnly, SameSite session cookie. Reloading the same tab resumes the project. Project directories expire after 24 hours and survive pod replacement until expiry; imported and sliced files remain in Bambuddy's library. Print dispatch reservations do not expire automatically.

The server requires the sliced preview to have been loaded and reviewed, (toolpaths are read from the sliced file when the slicer omits a thumbnail), checks the model revision, printer connection/state/alerts, nozzle and spool, and reserves the printer before dispatch. Duplicate requests return the recorded receipt. A lost dispatch response is uncertain, never retried. A reservation remains until a later status read observes this model printing. The app tracks progress, pauses, and completion only when the reported filename matches this project, so an unrelated print does not appear as its progress. If an observed print returns to idle without reporting completion (as the P1S does after a homing failure), the app reports that it stopped and preserves the original receipt. It never automatically retries that attempt. If the command never starts (including Developer Mode failures), an operator must verify the printer and Bambuddy queue before removing the corresponding dispatch-<printer-id>.json receipt under the data directory. Restarting the deployed app preserves these receipts and never retries a print command.

The app uses the tailnet as its access boundary. Session cookies isolate projects between browsers; they are not user accounts. Mutations require a same-origin custom header. Public model downloads reject private/LAN/tailnet addresses, pin DNS resolutions, validate each redirect, and enforce byte/time limits. Health/readiness probes remain independent of Bambuddy availability.

How it deploys

  1. Merge to main. build.yml tests, builds and pushes the arm64 image, then commits its :sha-<commit>@<digest> into k8s/deployment.yaml on main (expect those bot commits).
  2. home-cloud's Flux printit Kustomization (source: the printit GitRepository, cloned from Forgejo over its in-cluster Service) applies ./k8s into namespace printit.
  3. The Tailscale operator creates a ts-printit-* proxy and serves https://printit.<tailnet>.ts.net. The first HTTPS request hangs ~30s while it does ACME; pre-warm from any tailnet host with tailscale cert printit.<tailnet>.ts.net.

Changes under k8s/ alone don't rebuild the image; Flux picks them up on its next source poll.

Contract between the app and k8s/

Keep these when you replace server.mjs, or update k8s/ in the same change:

  • Listens on PORT (default 3000), container port name http.
  • GET /healthz (liveness) and GET /readyz (readiness) return 200 when up. Keep /readyz about this process, not bambuddy. If bambuddy being down made printit unready, the tailnet Ingress would 502 instead of showing an error.
  • Reads BAMBUDDY_URL for the bambuddy base URL. Default in-cluster: http://bambuddy.bambuddy.svc.cluster.local. For local dev, point it at https://bambuddy.tailb2e8f2.ts.net (npm run dev does this).
  • Model generation requires explicit FOUNTAIN_URL, FOUNTAIN_AGENT_ID, and FOUNTAIN_API_KEY. The server does not fall back to personal CLI credentials. Without configuration, upload, links, sample geometry, and printing still work. The manifest references optional printit-secrets/FOUNTAIN_API_KEY, supplied by the Infisical project printit (printit-d-xv-z). Keep this account-wide key server-side. The modeler uses ephemeral workspaces with sandbox_api_access: none.
  • Runs as uid 1000 (node) with a read-only root filesystem. /tmp is an emptyDir and /data holds the persistent projects and print receipts. Anything that writes elsewhere needs a volume in k8s/deployment.yaml.

Talking to bambuddy

bambuddy is FastAPI under /api/v1, and auth is disabled on this cluster, so no token is needed. Its pod is hostNetwork on one node but the ClusterIP Service fronts it normally. Verified reachable from an ordinary pod.

Endpoints exercised so far (see server.mjs for the client):

call notes
GET /printers/ list. Includes access_code, a credential. Do not surface it.
GET /printers/{id}/status live state, temps, hms_errors, progress.
GET /library/files?folder_id=1 the file library.
POST /library/files/{id}/slice body selects printer/process/filament presets; async, 202.
POST /library/files/{id}/print?printer_id=1 uploads over FTP and sends the MQTT print command.
POST /printers/{id}/logging/enable, GET /printers/{id}/logging raw MQTT log, the only way to see the printer's reply to a print command.

Gotchas learned while proving the pipeline (2026-09-06):

  • The slice dialog's default presets can be for the wrong printer (an H2D process and ABS filament for a P1S with PLA loaded). There is no P1S process preset; the P1S uses the @BBL X1C process presets.
  • bambuddy's developer_mode flag for a printer is a false positive. If the printer ignores a print command and never heats, read the MQTT log: err_code 84033543 means Developer Mode is off on the printer's own screen.
  • kubectl rollout restart on any Deployment here is reverted by Flux. Delete the pod.

Local dev

npm ci
npm test
npm run dev                      # http://localhost:3000, talks to bambuddy over the tailnet
docker build --platform linux/arm64 -t printit:dev .
docker run --rm -p 3000:3000 --read-only --tmpfs /tmp \
  -e BAMBUDDY_URL=https://bambuddy.tailb2e8f2.ts.net printit:dev

For UI testing with no physical printer access:

npm run build
node test/preview-server.mjs       # http://127.0.0.1:3188 (simulated printer)

The simulator uses deterministic agent/model/preview/estimate fixtures; it never contacts Fountain or Bambuddy. Integration tests verify reference-image forwarding, agent isolation, lost-response recovery, revisions, repair and bounded downloads, as well as fitted geometry being the actual uploaded file, compatible presets, review gates, session isolation, stale revisions, printer preflight, and ambiguous/duplicate dispatch behavior.

Check manifest changes render before pushing:

kubectl kustomize k8s

Runtime secrets

The printit-service Fountain key lives in the printit Infisical project's prod environment. The printit-operator identity has project viewer access and Kubernetes auth restricted to printit/printit-infisical. The app receives the operator-managed printit-secrets Secret; the modeling agent receives no callback token. Rotation uses Infisical's auto-reload annotation. Use the /add-secret-via-infisical skill (in home-cloud) when adding runtime keys.