Safely slice, preview, upload, monitor, and start Snapmaker U1 prints remotely through Telegram, OrcaSlicer, Moonraker, and an optional local AI agent.
Send an STL, 3MF, or ZIP from your phone. The toolkit analyzes the model, recommends print settings, slices it locally with OrcaSlicer, generates plate previews, verifies the loaded filament and toolhead, photographs the print bed, and asks for explicit approval before starting.
- Print to a Snapmaker U1 from your phone through Telegram.
- Slice STL and 3MF files with headless OrcaSlicer on Linux, WSL, or Windows.
- Preview model orientation, plate layout, toolpaths, and important print settings.
- Upload G-code through the U1's Moonraker/Klipper LAN interface.
- Verify toolheads and filament before heating or motion.
- Receive first-layer, final-layer, and completed-print camera photos.
- Require a fresh bed-camera image and human approval before every toolkit-started print.
- Run the deterministic CLI tools without AI, or add Hermes with a local LLM for the phone workflow.
The AI does not receive unrestricted printer control. Deterministic scripts own slicing, verification, uploading, monitoring, and the single-use print-start approval gate. See the safety model for the exact boundary.
This is how AI should touch physical machines: plan, explain, preview, ask, verify, then act only within a narrow approved boundary.
A local model takes a ZIP of eight STLs from Telegram to a reviewed printing plate in about 160 seconds, entirely on local hardware. Nothing starts until a human approves the fresh bed photo.
STL.Upload.Flow.mp4
Start here: print from your phone · install the toolkit · set up Telegram · run headless OrcaSlicer · troubleshoot
One flow handles everything. Send a single STL or a zip of twelve — a lone model is just a kit of one, auto-detected, same entrypoint, same safety boundary:
- Send a model. A
.stl/.3mf, or a zip of STLs (the common Printables shape). The workflow ingests every part: footprints measured, oversized parts flagged, hostile archives refused with a clean error instead of a crash. - Answer one decision form — parts, print head, orientation, supports,
profile. On a tool-capable model it renders as native buttons (one
submit); on a small local model it falls back to a typed one-liner, or a
staged one-question-per-turn flow. The form surfaces live U1 state (which
filament is actually loaded on which head) and Orca's real mesh-topology
verdict (
floating cantilever/clean/ overhang fraction) so you pick the pose Orca actually prefers. Either way a script parses and validates the answer; the model never interprets it — only an opaque form id rides through the model, and conflicting input fails loudly, never a silent guess. - Arrange + slice through OrcaSlicer onto as many plates as the bed needs — T0→T<chosen> rewriting, Snapmaker thumbnail injection, real Orca warnings surfaced, and a gcode-extent guard that refuses any plate whose extrusion would leave the bed (built from a real incident, not a hypothetical).
- Review two corroborating previews derived from the sliced gcode (the
real toolpath): a precise top-down footprint and a 3D plate view of the same
per-part geometry with height added — same parts, same colors, same
positions the printer will execute. Plus a
review.mdflight plan generated from the gcode's own config block: what will print, the ~12 settings that matter, your decisions and overrides. - Upload every plate to the U1's Moonraker storage with
print=false(files land; the printer does NOT start). - One bed-clear decision. A fresh photo of the bed from the U1's onboard camera arrives with the previews. Reply YES to start now, or NO to keep the gcode uploaded without printing. The YES is redeemed by the gateway itself — the workflow hands the model no start command, so a misbehaving or prompt-injected agent has nothing to fire. The yes rides a single-use token bound to the plan's revision + gcode hash and to your operator identity in your private chat — if anything changed since you looked, or the reply came from anyone else, the start refuses instead of printing stale state.
- Gated start with a last exit. The gate re-verifies material against what's physically loaded, then opens a ~120s grace window before any command reaches the printer: tap the CANCEL button on the countdown, or reply CANCEL — both handled model-free by the gateway, so neither can be swallowed by the agent's turn. Every outcome (started, cancelled, or refused-with-reason) is messaged to you by the machinery, never narrated by the model. Plate 1 is the only toolkit-started plate; plates 2..N start from the Snapmaker app — the watchdog photographs every plate either way.
- Monitoring takes over — first-layer photo, last-layer check, completion (see Always-on print monitoring).
Steps 1–5 are useful as CLI utilities even if you never touch an AI agent. Steps 6–8 are where the operator gate makes the difference between "AI presses print" and "AI safely shows you the print so you can press it."
The same entrypoint and the same bed-clear gate handle three more moves:
- Reprint. Say "reprint" (no file needed) and pick from your recent jobs. The gcode already on the printer is reused — no re-slicing — the original previews and review doc come back with a fresh bed photo, and the normal gate runs with the same drift and identity checks.
- Quantity. On a single part, ask for 1–9 copies; they pack onto the plate through the same arranger and split across plates when the bed fills.
- Advanced settings. An optional screen off the review card exposes infill density and pattern, wall count, brim, fuzzy skin, and tree-vs-grid supports — each defaulting to the profile's own value, each verified into the sliced gcode.
- It is not an autonomous printer driver. No agent in this stack can start a print without an explicit operator yes/no. Normal starts are approved against a fresh U1 camera photo captured in-the-moment; if the camera is unavailable, the only alternative is an explicit, audited manual bed-verification path (never a silent skip).
- It is not a generic slicer wrapper. Specific profile resolution, T0→T rewriting, Snapmaker thumbnail injection, and Moonraker storage discipline are baked in for the U1.
- It is not a multi-printer abstraction yet. The safety model and event contract are portable in principle. The implementation is U1-specific by design until the U1 experience is solid.
- It is not a Hermes-only project. Hermes is the convenient remote-control layer. Every workflow step has a CLI form and JSON event stream — wrap it with whatever you want.
Hermes — and any other AI agent layered on top — can recommend, explain, and prepare a print, but the U1 toolkit owns the final safety checks and will not perform printer-affecting actions without an explicit operator approval tied to a specific request ID.
The model is never given a way to start a print. The workflow hands the agent no start command at all — when you reply YES at the bed-clear prompt, the gateway itself redeems it and runs the start gate; the agent runs nothing. That YES is single-use and bound to your operator identity in your private chat, so a wrong sender, a wrong conversation, or a stale plan all refuse. The agent's only unattended power over the printer is in the safe direction: it can help you cancel a pending start, never begin one. This boundary was not designed on paper — it was forced by a live incident where an earlier build let the model fire a start it had been handed, then hardened across several rounds of adversarial review. In the default single-host deployment a deliberately hostile agent is contained (every start is audited, gated, and cancellable) rather than cryptographically prevented; the separate-user boundary that would make it prevention is on the roadmap. The full boundary, and that honest limit, are documented in docs/SAFETY.md.
The default lifecycle:
read state → slice → preview → upload-only → operator approval → start → monitor
Actions that always require explicit operator confirmation:
- Starting a print
- Resuming or canceling a print
- Heating nozzle or bed
- Moving axes
- Clearing alarms
- Changing tool state
- Anything that affects the physical printer
The workflow fails closed. If a check is unsure, it stops and asks rather than guessing. Bed-clear verdicts come from the operator looking at a real photo, not from the toolkit deciding the bed is "probably fine." Slicer profile mismatches abort BEFORE the slice. Upload that hits a filename collision asks before overwriting.
Every operator decision is concrete and tied to a specific artifact:
| Decision | What the operator sees |
|---|---|
| Parts / orientation | Parts thumbnail grid + Orca's mesh-topology verdict for the recommended pose |
| Tool / filament | Live U1 toolhead state ("T0: Generic white PETG (loaded)") |
| Preset | Recommended profile based on model class + your print history |
| Supports | Overhang verdict from a fast draft slice — Orca's real call, not face-angle |
| Pre-print review | A review.md flight plan generated from the sliced gcode's own config block — bound to the plan's revision + hash so what you read is what prints |
| Bed clear | A real, fresh photo of the bed from the U1's onboard camera. One yes/no. Default is no. |
If anything is unknown — printer state, tool, material, slicer metadata, bed visibility — the workflow stops and asks. No silent assumptions.
What the exchange actually looks like after the form is submitted:
Bot: (plate preview, 3D view, review.md, and a fresh bed photo arrive) Sliced plate, review doc, and a fresh bed photo are attached. Bed clear and ready to print? Reply YES to start now, or NO to keep the gcode uploaded without printing.
You: yes
Bot:
⚠️ Snapmaker U1 print starting in 120s. Reply CANCEL to abort. Ignore this to let the print start.
If a re-slice or plan change happened between the photo and the "yes," the start refuses and re-asks with the new revision instead of starting on stale state.
The full per-action breakdown (the test-operator fence, the grace-period cancel chain, and every allowed-vs-gated command) lives in docs/SAFETY.md.
None of this is aspirational: the full test suite runs in CI on every change (the deployed runtime is Python 3.13), and the cancel chain is live-verified on real hardware, including a reproducible, no-printer-needed drill anyone can run: docs/verify-cancel-hook.md.
The start decision needs an LLM + your explicit approval. Watching the print once it's running does not — that part is three quiet cron jobs, no agent turn, no LLM in the loop at all.
Why first-layer specifically matters: it's the earliest real tell a print is about to fail — bed adhesion problems, warping, a shifted part, wrong Z-offset all show up in the first few layers, long before you'd otherwise notice. A last-layer photo confirms it finished; a first-layer photo is the one that could actually save you hours of wasted filament and time if you catch it early.
| Job | Cadence | What it does |
|---|---|---|
u1_last_layer_watch.py |
every 1 min | Snaps a photo at first-layer (layers 2–5, the bed-adhesion check), at last-layer (final ~6 layers), and after a pause/resume (an extra confidence check). Delivers each straight to Telegram. Auto-dims the cavity LED a few minutes after the job completes/errors/cancels. |
u1_print_watchdog.py |
every 5 min | Silent health poll across any active U1 print, not just Hermes-started ones. Alerts once per distinct issue, with cooldown so it never spams. |
u1_print_history.py |
every 5 min | Appends to a durable print ledger. |
All three run in Hermes' no_agent cron mode — a plain script invocation with no persona and no model call, so there's nothing for a weak or a strong model to get wrong, and nothing that can fabricate a milestone that didn't happen (the photo either exists or the job says nothing). They watch every active print, whether it was started through this toolkit, the Snapmaker app, or anything else touching the same Moonraker.
The toolkit ships as three layers that build on each other. Pick your mode, then install below.
Scriptable, deterministic, single-purpose tools that a U1 owner can use directly:
- Slice + preview a model
- Inspect printer state, profiles, print history
- Generate orientation renders
- Upload a job with
print=false - Review G-code metadata before printing
These are designed for shell scripts, cron jobs, manual workflows. No AI required.
A multi-step state machine that walks an operator through the print decision. Emits structured JSON events at every step, so any frontend (Telegram bot, web UI, custom integration) can wrap it without re-implementing the logic.
This is the core product. It's what makes the toolkit feel like a responsible assistant instead of a generic API wrapper.
A bundled Hermes skill (3d-printer-slicing-automation) that lets a Telegram-bridged Hermes agent drive the operator workflow on the user's behalf. The agent:
- Surfaces the workflow's questions and previews to the user verbatim
- Tool-calls the named scripts (never invents its own slicing path)
- Never decides bed-clear status on its own — the operator does, looking at a real photo
The skill is designed to work on small local models (gemma4-26b-64k and below) via Ollama. See Hermes integration for the full setup.
One install path. Everything below assumes a U1 reachable on your LAN and a Linux (or WSL) host. Native Windows is experimental — see docs/WINDOWS.md for what differs and how to validate it.
Requirements: Python 3.9+, numpy + Pillow (via requirements.txt), an
OrcaSlicer 2.4.0+ CLI binary
(extracted AppImage is fine — full steps in
Headless slicing setup), and network
reachability to your U1's Moonraker port (default 7125).
git clone https://github.com/bbolinger/snapmaker-u1-toolkit.git
cd snapmaker-u1-toolkit
python3 -m pip install -r requirements.txt
# Point the toolkit at your printer (.env is auto-loaded on first config read)
cp .env.example .env # edit: set SNAPMAKER_U1_HOST to your U1's LAN IP
# Fetch Snapmaker's stock U1 profiles (~217 files) + extract your own history
python3 tools/fetch_snapmaker_profiles.py
python3 tools/extract_profiles_from_printer.py # optional but recommended
# Verify (argparse usage text = your environment is ready)
python3 scripts/u1_slice_workflow.py --help
# Read-only status probe (no risk)
python3 scripts/snapmaker_u1_status.pyThe networked steps (extract_profiles_from_printer.py, snapmaker_u1_status.py)
time out until SNAPMAKER_U1_HOST points at your printer's real LAN IP. The
.env.example default 192.168.1.100 is only a placeholder, so edit .env
first, or skip those two until the printer is reachable.
On Windows (PowerShell) the same steps apply with Copy-Item .env.example .env
and backslash paths; the data dir defaults to
C:\Users\<you>\.local\share\snapmaker-u1 (override with
$env:SNAPMAKER_U1_DATA_DIR).
Choosing a Python interpreter. The workflow needs numpy and Pillow on
the Python that runs it and auto-detects a working interpreter (first that can
import numpy, PIL wins): $U1_TOOLKIT_PYTHON, then
/opt/hermes/.venv/bin/python, then a project-local venv/.venv, then the
Homebrew paths. If none has the deps, it exits listing every path it tried and
how to fix it. Cleanest isolated setup:
python3 -m venv venv
venv/bin/pip install -r requirements.txt
export U1_TOOLKIT_PYTHON=$PWD/venv/bin/python # add to your shell rc to persistConnection, data-dir, and LED behavior are covered in Configuration. If something fails, check TROUBLESHOOTING.md.
The canonical entry point for a model or a kit zip (kit-of-one auto-detected):
python3 scripts/u1_slice_workflow.py model.3mfAgent/Telegram wrappers should consume the event stream instead of re-implementing the workflow:
python3 scripts/u1_slice_workflow.py model.3mf --json-eventsSafe headless proof run — decisions passed as flags, upload-only, no printer
start (list your profile slugs with python3 scripts/u1_profile_picker.py):
python3 scripts/u1_slice_workflow.py model.3mf \
--tool T1 --material PETG --orient auto \
--profile 0_20_strength_snapmaker_u1_0_4_nozzle \
--supports auto --upload-only --yesWithout profiles the workflow exits with a setup_required event and points
you back at the fetch/extract tools from Install.
For the design rationale, architecture, and acceptance criteria, see
docs/DESIGN-CONTRACT.md. For the public event
contract (every event the workflow + audit log emit, with payload shapes), see
docs/events.md.
Install on Linux. The deploy scripts are bash and install.py targets a
Linux venv (bin/, lib/pythonX.Y/). Run it inside the Hermes container, on a
Linux host, or under WSL / Git Bash on Windows — not raw Windows cmd. Hermes
typically ships numpy + Pillow in its bundled venv (verify:
/opt/hermes/.venv/bin/python -c 'import numpy, PIL; print("ok")').
Run each step and let it finish before the next. Step 1 asks a y/N you have to
answer, so do not paste the whole block at once.
1. Install the bundled skill (answer y at the prompt):
hermes skills install bbolinger/snapmaker-u1-toolkit/skills/3d-printer-slicing-automation2. Deploy the workflow scripts to the runtime paths the skill calls into:
bash deploy_to_runtime.sh3. Install both Hermes plugins and patch the gateway. This installs the
u1-form tool plugin and the snapmaker_u1 hook plugin (auto-skill load,
next-action guard, and the image / review-doc attachment injector). Add
--venv <path> if your Hermes venv is not /opt/hermes/.venv:
python3 adapters/hermes/install.pyinstall.py loads two plugins: u1-form and snapmaker_u1 (a pip entry point,
installed editable so git pull updates it). Without this step the form, the
auto-skill trigger, and the image/review-doc attachments do not load. Watch for
its [4/6] install the snapmaker_u1 hook plugin and a [6/6] verify ending
OK: hooks=...transform_llm_output.
4. Install both gateway hooks (the operator YES that starts a print and the reply/tap CANCEL that stops one), restart the gateway, and verify:
bash tools/install_hermes_u1_hooks.sh
hermes gateway restart
bash tools/install_hermes_u1_hooks.sh --verifyIf you run these from inside a Hermes Desktop or gateway chat, run hermes gateway restart from a separate terminal outside Hermes. The gateway
refuses to restart itself from within its own process (it would kill the command
mid-run), so the hooks stay unloaded until you restart it externally.
The YES/CANCEL hooks bind to one operator in one private Telegram DM. With a
single user id in TELEGRAM_ALLOWED_USERS the binding resolves itself;
otherwise set it explicitly in the runtime .env:
U1_OPERATOR_BINDING=telegram:<your-numeric-telegram-user-id>Without the hooks installed, a YES at the bed-clear prompt does nothing
(fail-safe: the printer never starts) — --verify and the deploy script
both tell you loudly.
The deploy script verifies the deployed workflow actually starts (✓ workflow starts cleanly); override target paths via U1_DEPLOY_SCRIPTS /
U1_DEPLOY_TOOLS / U1_DEPLOY_SKILL / U1_DEPLOY_PROFILES if your layout
differs from the Hermes default.
The skill tells Hermes to call scripts/u1_slice_workflow.py, follow the
workflow's events, default to upload-only, and fail closed at the bed-clear
start gate.
The button-based form mode (the rich Telegram UX in the demo) asks the local
model to emit one tool call. Small local models are inconsistent at tool-calling,
so form mode has hard serving requirements — verified end-to-end on
gemma4-26b-64k via Ollama on an NVIDIA Quadro P6000:
-
Ollama 0.31.1 or newer. Ollama 0.30.x has a gemma4 tool-call parser bug (#15539, #15798, #15943): the model's tool call leaks into the message content as raw template tokens (
<|channel|>,<|"|>), the parser misses it,finish_reasonisstop, and the agent stalls with no buttons. -
Run the model at low temperature (~0.2) for tool turns. Gemma's default Modelfile ships
temperature 1, which is unreliable for tool calls (~2 of 3 succeed in testing — one run in three strands the operator). A temp-0.2 variant is 3 of 3. Create one (it shares the same weights blob — no extra disk):printf 'FROM gemma4-26b-64k:latest\nPARAMETER temperature 0.2\nPARAMETER num_ctx 65536\n' \ | ollama create gemma4-26b-64k-tool -f -
then point your agent's model at
gemma4-26b-64k-tool. -
The toolkit already does its part. The
kit_formevent carries only a shortform_id; the full form definition is persisted to disk and loaded by the form plugin, so the model never has to reproduce a large nested schema in its tool call (what small models fail at). Nothing to configure — just don't downgrade the bundledu1-formplugin.
All three are needed together: on 0.30.8 the model failed even with the flat
form_id call; on 0.31.1 the flat call works, but only at low temperature is it
reliable. If tool calls still fail, fall back to text mode
(--interaction-mode text) — the staged one-question-per-turn flow uses only
simple terminal calls that even small models handle reliably.
Hardware note (Pascal / older GPUs). Ollama 0.31's
cuda_v13runtime dropped Pascal (compute capability 6.1); it falls back to the bundledcuda_v12, so a P6000 / GTX-10-series still works today — but a future Ollama that dropscuda_v12would break it. BenigndriverInitFileInfo ... result=11lines at startup are that fallback, not a failure.
If you fork this skill or write your own, Hermes' platform gateways (Telegram, Discord, Signal, etc.) deliver media to the user by scanning the agent's reply text for bare absolute file paths ending in known media extensions and auto-attaching whatever exists on disk. There is no files=[...] tool parameter the agent needs to call. See gateway/platforms/base.py:extract_local_files() in Hermes 0.15.2 for the canonical implementation.
What this means for your skill prompt:
- ✅ Tell the agent: "emit the absolute path bare in your reply text"
- ❌ Do NOT tell the agent: "attach the file via the reply tool's files parameter"
- ❌ Paths inside backticks or fenced code blocks are skipped — the agent must emit them as bare text
This caught me out during the first live test — the agent kept claiming it would "attach" renders but the gateway saw nothing to extract. See TROUBLESHOOTING.md for the full diagnosis if you hit the same.
| Script | What it does |
|---|---|
u1_config.py |
Centralized host/port resolution (env > JSON > default) |
u1_camera.py |
Camera capture via Snapmaker-specific websocket camera.start_monitor; auto-on/restore the cavity LED for each capture via u1_led.photo_wrap |
u1_led.py |
Cavity LED helper — CLI (status / on / off / set --r/g/b/w / is-on) and photo_wrap() context manager. The U1's cavity LED is white-only (white_pin: PA10 in printer.cfg); the 4-channel API matches Klipper's interface, only WHITE has visible effect |
u1_toolmap.py |
Multi-tool material gate — declared vs detected material check |
u1_preflight.py |
Combined Moonraker state + camera freshness packet for "is it safe to start?" |
u1_upload_gcode.py |
Upload-only (print_started=false) with gates: idle state + tool/material match |
u1_slice_workflow.py |
Canonical end-to-end STL/3MF entry point: orient → render → slice → preview → upload-only/start gate |
u1_kit_workflow.py |
The unified kit workflow behind it — ingest, one decision form, arrange, slice, previews, bed-clear gate |
u1_last_layer_watch.py |
Watch active print for first-layer (2–5) and "last ~6 layers" milestones, snap photos; also auto-dims the cavity LED 5 minutes after complete/error/cancelled (U1_LED_OFF_DELAY_SEC overrides) |
u1_print_watchdog.py |
Quiet cron-driven health watcher with per-issue cooldown to avoid notification spam |
u1_print_history.py |
Append-only JSONL print ledger + canonical upserted JSON |
snapmaker_u1_status.py |
Read-only status probe |
snapmaker_u1_snapshot.py |
Websocket camera trigger helper |
tools/extract_profile_from_gcode.py |
One-shot extractor — turn a successful G-code into Snapmaker Orca process + filament JSONs |
tools/extract_profiles_from_printer.py |
Auto-pull recent G-codes off your U1 over Moonraker, run the extractor against each — one command, gets your real print history into profiles/from-printer/ |
tools/fetch_snapmaker_profiles.py |
Fetch Snapmaker's official U1 stock profiles (machine + process + filament) from the upstream Snapmaker/OrcaSlicer GitHub repo into profiles/snapmaker-stock/ |
tools/gcode_inject_thumbnail.py |
Add Snapmaker-app preview thumbnails to headless-sliced G-code (PIL renderer + base64 splice) |
tools/render_stl_orientation.py |
Pre-print orientation review — 4-view PNG (isometric, front, side, top) with overhang faces highlighted in orange |
u1_config.py resolves two things — the connection to the printer, and
the data dir where runtime state lives (configs, photos, ledgers).
- Environment variables:
SNAPMAKER_U1_HOST,SNAPMAKER_U1_PORT - JSON file: location from
SNAPMAKER_U1_CONFIGenv, default<data-dir>/u1_config.json(contains{"host": "...", "port": 7125}) - Hardcoded default: port 7125 only — host is required
SNAPMAKER_U1_DATA_DIRenv var (explicit override)/opt/data/snapmaker_u1if it exists (auto-detects Hermes-style installs — for the agent setup these scripts came from)~/.local/share/snapmaker-u1(community default, follows XDG Base Dir)
All host/port/data-dir lookups happen on first call — import u1_toolmap (or
any other script) never touches disk for config. The lookup only fails when
you actually run a command without any configuration.
See .env.example for a starting template.
The U1's cavity_led is white-only — Snapmaker's shipped printer.cfg
defines it as [led cavity_led] / white_pin: PA10, no R/G/B. Klipper's
[led] interface exposes all four channels regardless, but only the W
channel is physically wired. The toolkit drives the LED in two places
so the operator doesn't have to think about it:
- Every camera capture (
u1_camera.py photo, and therefore every milestone photo fromu1_last_layer_watch.py) is wrapped in au1_led.photo_wrap()context manager:- LED already on → no change, no flicker.
- LED off → turn on white (W=1), settle ~300 ms for the camera's auto-exposure, capture, then restore the LED to off.
- 5 minutes after a print finishes (
print_stateenterscomplete,error, orcancelled) the LED is turned off, once per print. If you manually turn it back on, it stays on — the watcher dedups byjob_key = filename|total_layerand won't re-fire for the same print.
Tuning / disabling:
U1_LED_OFF_DELAY_SEC=Nenv var — grace window before auto-off. Default300. Set0for immediate. Set a large value (e.g.86400) to effectively disable the auto-off without removing the wiring.- The wiring is fail-soft: if
cavity_ledisn't configured on your printer (or the LED endpoint errors), the LED helper logs to stderr and the photo/watcher keeps doing its primary job. - Manual control via the CLI:
u1_led.py status / on / off / set --r --g --b --w.
Why: photos taken at first/last-layer/post-resume milestones need the LED on to be useful, but leaving the cavity bright forever after a finished print is wasteful and surprising. The 5-minute grace gives you time to inspect the bed before it goes dark.
Your profiles should be yours: extracted from your own successful prints,
mapped to your own extruders. The toolkit ships with an empty profiles/ dir on
purpose and gives you three honest sources: Snapmaker's stock profiles,
profiles extracted from prints that already succeeded on your printer, and
your own hand-tuned files. The full guide (first-run setup, supports
auto-detection, and building per-extruder profiles from print history) is in
docs/PROFILES.md.
Slicing from the CLI in a container, CI pipeline, or agent workflow, with no OrcaSlicer GUI: which build to use, the 3-profile load order, a real inheritance pitfall and its workaround, and the orientation-review and thumbnail-injection tools. Full guide: docs/HEADLESS.md.
The toolkit ships a small checker that compares your installed orca-slicer version against the upstream latest release. It does nothing unless you wire it into your scheduler. Cloning the repo does not subscribe you to anything.
To enable, add one line to cron (Linux/macOS):
0 7 * * * /usr/bin/python3 /path/to/snapmaker-u1-toolkit/tools/check_for_updates.py
Behavior:
- Silent when you're current. No stdout → no cron email.
- Single-line stdout when an update is available — cron mails it via your usual cron-email setup. Example:
OrcaSlicer 2.4.1 available (you have 2.4.0). Patch (bug fixes, likely safe). Release notes: https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/v2.4.1 - Refuses to query GitHub more than once per 24h regardless of how often you invoke it (cache at
~/.cache/snapmaker-u1-toolkit/update-check.json).--forceoverrides for one-off "tell me now" runs. - Returns silently when GitHub is unreachable or the binary isn't present. Never breaks your cron with stray stderr.
Compatibility note: Snapmaker upstreamed the U1 vendor profile into OrcaSlicer 2.4.0, and tools/fetch_snapmaker_profiles.py pulls fresh stock profiles from that upstream — patch/minor upgrades should keep slicing U1 prints. Major-version bumps may change CLI flags or profile schema — re-run the EGO trimmer regression after upgrading. The notifier's risk label ("patch / minor / major") flags this in the alert text.
If your orca-slicer binary lives anywhere other than /opt/data/tools/orcaslicer/squashfs-root/bin/orca-slicer (Hermes-container default), pass the path explicitly OR set the ORCA_SLICER_BIN environment variable in your crontab, otherwise the script silently can't probe your installed version and you'll never see notifications.
CLI:
python3 tools/check_for_updates.py # daily-cached check
python3 tools/check_for_updates.py --force # bypass cache, hit GitHub now
python3 tools/check_for_updates.py --orca-bin /path/to/orca-slicer # one-off
ORCA_SLICER_BIN=/path/to/orca-slicer python3 tools/check_for_updates.py # persistent env
Real reverse-engineering notes from getting these scripts working — the kind of stuff Snapmaker doesn't document publicly:
| Doc | Topic |
|---|---|
references/snapmaker-u1-lan-probe-2026-06.md |
Open ports, working endpoints, API key handling |
references/snapmaker-u1-camera-websocket.md |
Snapmaker-specific camera.start_monitor websocket method |
references/snapmaker-u1-headless-slicing-and-toolhead-notes-2026-06.md |
OrcaSlicer CLI for headless slicing, tool naming gotchas |
references/snapmaker-u1-filament-detection-api-2026-06.md |
Filament presence/material detection objects |
references/snapmaker-u1-printhead-numbering-and-start-workflow-2026-06.md |
T0..T3 mapping to extruder..extruder3 |
references/snapmaker-u1-last-layer-photo-watch-2026-06-21.md |
Last-layer event detection for milestone photos |
references/snapmaker-u1-toolmap-script-2026-06.md |
Material-gate design rationale |
references/snapmaker-u1-orca-moonraker.md |
OrcaSlicer + Moonraker integration |
references/snapmaker-u1-research.md |
First-pass research summary |
python3 -m venv .venv
source .venv/bin/activate
pip install pytest
pip install Pillow numpy # only needed for the thumbnail-injector tests
pytest -vThe suite (CI runs it on every pull request; the deployed runtime is Python
3.13) covers: config resolution (incl. 3-tier data-dir, .env
auto-loader with quoted/commented/walk-up edge cases, import-without-config
regression lock, and a smoke-runner that exercises every script's main()
to catch leftover undefined refs), material gate (incl. fail-closed on
corrupt map), upload pre-checks, G-code metadata parsing, print-history
ledger (incl. atomic-write contract + tmpfile cleanup on failure), profile
extraction (incl. multi-tool slice handling — PETG;PETG;PLA;PLA →
right value for the actual tool), thumbnail injection, upload-time
thumbnail wiring, status-probe safe_to_upload parity with the actual
upload gate, preflight --host override correctness, STL parsing + view
rotations + overhang detection + 4-view orientation sheet rendering,
bundled machine-profile completeness (standalone, klipper gcode flavor,
4 extruders, required slicing fields), printer-side profile extraction
(Moonraker list + download mocked, friendly errors).
Tests use mocked Moonraker responses — no real printer required. The
thumbnail-injection tests importorskip PIL/numpy, so they're harmless
to omit if you only want to run the safety-script tests.
Each tagged release is validated before publish: the full test suite in CI (on the deployed Python 3.13 runtime), a fresh-clone install and script-help smoke test, the active-print upload-gate safety check against a mocked Moonraker, and, for releases that touch the safety boundary or the operator flow, a live run on a real U1. Per-release evidence lives in the CHANGELOG.
- Single-printer scope: scripts assume one U1. Multi-printer would need namespacing in the config + per-printer state dirs.
- Cron / always-on cadence: the watchdog, last-layer, and history scripts are written to be cron-driven (the table above shows the defaults; the intervals are yours to set). They keep state on disk and are idempotent across runs, but they're not daemonized; your scheduler (cron, systemd timer, Hermes' cron, etc.) owns the cadence.
- U1 firmware coupling: tested against Snapmaker U1 firmware on the version that ships Moonraker on port 7125. Other Snapmaker models, or future firmware revisions that change the
print_task_config/filament_detectobject shape, may surface field gaps. Thereferences/docs capture what the current firmware does emit — start there if you're debugging a field-shape mismatch.
MIT — see LICENSE.
PRs welcome, especially:
- Additional reference docs as new firmware behaviors are reverse-engineered
- Material gate enhancements (multi-tool prints, prime-tower extruder assignment)
- Multi-printer support (namespacing config + per-printer state dirs)
Please run pytest before submitting — all tests should pass. See
CONTRIBUTING.md for setup, conventions, and the
safety-model rules that PRs need to respect.
These scripts were developed and validated against a real Snapmaker U1 over weeks of daily use. Real prints span single models and multi-part kits, generic and brand-specific PETG plus PLA, supports and no-supports, and run times from ~25 minutes to ~12 hours. The safety gate has both blocked a real material mismatch before any heat and carried full prints to completion, driven live over Telegram by a local model.
Philosophy: profiles should be YOUR profiles, extracted from YOUR successful prints, mapped to YOUR extruders. The included profiles/ directory shows the shape of those files but is environment-specific. The toolmap gate enforces per-extruder material assignment so wrong-slot mistakes don't waste filament.
Bug reports and PRs from other U1 owners welcome — especially the profile-extraction methodology being tried on different setups (smooth PEI, glass beds, other PETG brands, multi-tool configurations).


