Compare commits

..

28 Commits

Author SHA1 Message Date
Ian Chua
07c62112b8 Reconcile implementation split with PR tip 2026-08-04 18:12:51 +08:00
Ian Chua
f23e4963bf fix: merge access codes into one 2026-08-04 18:12:43 +08:00
Andrew
95279f7084 docs: document the printer-agent subsystem 2026-08-04 18:12:43 +08:00
Andrew
1014558c91 Keep printer-agent progress in sync
Keep the shared task progress aligned with agent
reports that lack Bambu cloud task identity.

Release the lazily allocated task during reset to
avoid leaks when machine objects reconnect.
2026-08-04 18:12:42 +08:00
Andrew
6515062a3a fix: stop Qidi slot parse throwing on null 2026-08-04 18:12:42 +08:00
Andrew
8153e26b6d fix: start stream when camera URL changes 2026-08-04 18:12:42 +08:00
Andrew
79fd33d49a Show Snapmaker U1 camera in Device tab
The U1 exposes no /server/webcams/list entry;
its camera only captures after an explicit
camera.start_monitor RPC, which the Moonraker
websocket executes unauthenticated but only
answers over MQTT - so the call is fire and
forget.

Start the camera when the camera view is
shown and renew every 300 s: the printer
retires the capture task at ~362 s and
stop_monitor is accepted but ineffective,
so teardown is simply to stop renewing.

Frames land in monitor.jpg as still JPEGs
(~2 fps at interval 0), so the webview loads
a local HTML wrapper that repolls with a
cache buster.
2026-08-04 18:12:41 +08:00
Andrew
cdb662d4b2 Move Moonraker commands off the UI thread
Pause/resume/stop, g-code sends, temps, and
light ran synchronous HTTP on the UI thread,
freezing the app up to 10s per click on slow
or unreachable printers.

Run them on a single agent-owned FIFO worker
so g-code ordering is preserved, while command
translation stays synchronous so unsupported-
command dialogs still work.

Add a pending-disabled state to the pause,
resume, and abort buttons for Moonraker-family
printers: the icon only flips once the
WebSocket reports the real state, which also
rules out double-click races.
2026-08-04 18:12:41 +08:00
Andrew
a27fa7b5df Fix multi-color filament logic
Reuse color decoding across functions to improve
code readability and maintain consistency in
multi-color filament handling.
2026-08-04 18:12:40 +08:00
Andrew
cdeed107c5 Keep Bambu AMS dialect out of the agent waist
M620 is Bambu firmware dialect, not a
neutral command. Composing it in
MachineObject let non-Bambu agents
(Moonraker/Klipper) forward it and
report success on firmware that
cannot run it.

Agents now own the dialect: the
default refusal on IPrinterAgent
returns not-supported so the UI
can say so; BBLPrinterAgent keeps
the byte-identical composition.
2026-08-04 18:12:40 +08:00
Andrew
be92f7e78c Surface Moonraker webcams and gate unrunnable controls 2026-08-04 18:12:39 +08:00
Andrew
76f9de4cfb fix: make Klipper macro lamp control reliable 2026-08-04 18:12:39 +08:00
Andrew
cd6d2cbf5e Stop blocking print on unreported nozzle data 2026-08-04 18:12:39 +08:00
Andrew
deaca0189f Bring Moonraker device panel to feature parity
The monitor panel showed wrong or missing
data for Moonraker printers, and its
controls did nothing.

Push payload now carries layer number and
total layers. Remaining time replaces the
wrong total_duration - print_duration
formula. The chamber light toggle maps to
Klipper SET_PIN / SET_LED, and pause,
resume and stop post to
/printer/print/{action}. Task thumbnails
resolve via /server/files/thumbnails onto
a new MachineObject thumbnail url.

Filament sync switches to pull mode so the
agent is queried on demand.

Not compiled or run.
2026-08-04 18:12:38 +08:00
Andrew
7d54ee286d fix: pin HTTP to prevent connection refusal
Set `use_ssl` to false to ensure Moonraker
connectivity, as the service uses HTTP rather
than HTTPS, preventing connection issues. Initialize
device info early for reliable name resolution.
2026-08-04 18:12:38 +08:00
Andrew
dddfc7a8ad Add printer-agent and plugin status tests
Ports the agent lifecycle, duplicate
agent-id, built-in-id clash and
status-resolution tests. The loader
runs on a detached worker thread, so
the lifecycle tests live in their own
executable. Tests install the
production unload-side registry
wiring themselves (no GUI in the
test binary) and register agents
manually so concurrent loads stay
deterministic.
2026-08-04 18:12:37 +08:00
Andrew
673bfb0dee Track BBLPrinterAgentPlugin.py 2026-08-04 18:12:37 +08:00
Andrew
b9e8c5a38c Gate agent mode behind use_printer_agents toggle
Replace per-printer auto-activation
(is_current_printer_agent_plugin)
with a global experimental AppConfig
toggle, default off: legacy
print-host behavior is unchanged
until the user opts in. The toggle
drives device-tab routing, print
button defaults, connect-button
visibility and sidebar layout, and
dedups machine-select dialog opens.
2026-08-04 18:12:36 +08:00
Andrew
a2a4ca20e4 Reset device selection on agent swap or unload (#124)
set_live_printer_agent centralizes
the swap: deselect the machine,
clear stale sidebar state and the
previous agent's Other Devices, then
install the new agent (or null when
its provider vanished). Plugin
load/unload callbacks refresh the
dropdown and re-run agent selection.
load_last_machine no longer falls
back to the first available machine.
2026-08-04 18:12:36 +08:00
Andrew
12075459d2 Replace fake-enum printer agent dropdown (#121)
A dedicated PrinterAgentChoice field
reads rows straight from the live
agent registry and stores the agent
id string, replacing the fake-coEnum
index mapping. The field moves to
TabPrinter and registers with the
searcher so UnsavedChanges renders
it; the PhysicalPrinterDialog copy
and its update hook are removed
(#125). switch_printer_agent now
resolves ids via
resolve_printer_agent_id.
2026-08-04 18:12:36 +08:00
Andrew
f3770f3106 fix: checkbox should depend on plugin is_loaded status 2026-08-04 18:12:35 +08:00
Andrew
90fbf1770f Resolve duplicate agent ID conflicts
Reject a printer-agent capability
whose agent ID is already owned by
another capability or built-in:
flag the plugin error, disable the
capability, and warn the user
instead of silently ignoring it.
2026-08-04 18:12:35 +08:00
Andrew
bde94ab37f Add support for runtime error status in plugins
Distinguish a loaded plugin whose
capability errored (RuntimeError,
warn-styled, stays checked) from a
load-time Error. Status now derives
via resolve_plugin_status(); enum
ordinal keeps dialog sort priority.
Unloading clears stale errors.
2026-08-04 18:12:34 +08:00
Ian Chua
6d25e1777e Working Moonraker and Qidi printer agent transport (#104)
Folds the Qidi AMS box-mapping print
overrides (apply_box_mapping +
start_* wrappers) that the transport
fix builds on.
2026-08-04 18:12:34 +08:00
Andrew
7ce26ca8e5 Harden send flow and separate upload failure recovery (#111)
* fix(send): harden FT send path + IP pre-flight UX

* Remove early returns
2026-08-04 18:12:33 +08:00
Andrew
367fcce634 Prevent loss of user access code on LAN reselect
Keep user access code intact to maintain access rights even if
device slot is unpopulated, ensuring continuous connection
and status message reception.
2026-08-04 18:12:33 +08:00
Andrew
9b3a44b339 Parse user print info on the UI thread to prevent heap corruption (#119)
get_user_print_info()'s HTTP fetch can run on a worker thread (e.g. BindJob),
but parse_user_print_info() mutates userMachineList (insert/erase/delete
MachineObject). on_machine_alive (SSDP) mutates the same maps on the UI thread
without locking, so parsing off-thread races the map and frees MachineObjects
out from under it -> heap corruption.

Keep all device-list mutation on the UI thread: parse inline when already on
the main thread, otherwise marshal via CallAfter so it stays serialized with
on_machine_alive.
2026-08-04 18:12:33 +08:00
Ian Chua
6384191102 Add developer flag for printer agents 2026-08-04 18:12:32 +08:00
300 changed files with 8650 additions and 11313 deletions

View File

@@ -465,57 +465,6 @@ static void stb_textedit_click(STB_TEXTEDIT_STRING *str, STB_TexteditState *stat
STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
y = r.ymin;
}
else
{
// In multi-line mode, clamp y to stay within the text vertical bounds.
// This lets the click still land at a valid location if the mouse is slightly
// above or below the text.
StbTexteditRow r;
int n = STB_TEXTEDIT_STRINGLEN(str);
int i = 0;
float base_y = 0, y_min, y_max;
// Get the first row to establish y_min and start the iteration
STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
if (r.num_chars <= 0)
{
state->cursor = 0;
state->select_start = state->cursor;
state->select_end = state->cursor;
state->has_preferred_x = 0;
return;
}
y_min = r.ymin;
y_max = base_y + r.ymax;
i = r.num_chars;
base_y += r.baseline_y_delta;
// Walk the remaining rows to find the bottom of the last row
while (i < n)
{
STB_TEXTEDIT_LAYOUTROW(&r, str, i);
if (r.num_chars <= 0)
break;
y_max = base_y + r.ymax;
i += r.num_chars;
base_y += r.baseline_y_delta;
}
// If the text ends with a newline, account for the empty trailing line
// so the cursor can be placed on it
if (n > 0 && STB_TEXTEDIT_GETCHAR(str, n - 1) == STB_TEXTEDIT_NEWLINE)
{
STB_TEXTEDIT_LAYOUTROW(&r, str, n);
y_max = base_y + r.ymax;
}
// Subtract half the last line height to avoid rounding issues when the mouse
// is just barely below the last line (keep cursor on the last line, not after the text)
y_max -= (r.ymax - r.ymin) * 0.5f;
if (y < y_min) y = y_min;
if (y > y_max) y = y_max;
}
state->cursor = stb_text_locate_coord(str, x, y);
state->select_start = state->cursor;
@@ -536,50 +485,6 @@ static void stb_textedit_drag(STB_TEXTEDIT_STRING *str, STB_TexteditState *state
STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
y = r.ymin;
}
else
{
// In multi-line mode, clamp y to stay within the text vertical bounds.
// This lets the drag keep working if the mouse goes off the top or bottom of the text.
StbTexteditRow r;
int n = STB_TEXTEDIT_STRINGLEN(str);
int i = 0;
float base_y = 0, y_min, y_max;
// Get the first row to establish y_min and start the iteration
STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
if (r.num_chars <= 0)
return;
y_min = r.ymin;
y_max = base_y + r.ymax;
i = r.num_chars;
base_y += r.baseline_y_delta;
// Walk the remaining rows to find the bottom of the last row
while (i < n)
{
STB_TEXTEDIT_LAYOUTROW(&r, str, i);
if (r.num_chars <= 0)
break;
y_max = base_y + r.ymax;
i += r.num_chars;
base_y += r.baseline_y_delta;
}
// If the text ends with a newline, account for the empty trailing line
// so the cursor can be placed on it
if (n > 0 && STB_TEXTEDIT_GETCHAR(str, n - 1) == STB_TEXTEDIT_NEWLINE)
{
STB_TEXTEDIT_LAYOUTROW(&r, str, n);
y_max = base_y + r.ymax;
}
// Subtract half the last line height to avoid rounding issues when the mouse
// is just barely below the last line (keep cursor on the last line, not after the text)
y_max -= (r.ymax - r.ymin) * 0.5f;
if (y < y_min) y = y_min;
if (y > y_max) y = y_max;
}
if (state->select_start == state->select_end)
state->select_start = state->cursor;

View File

@@ -0,0 +1,72 @@
# Printer agents
Printer agents let OrcaSlicer communicate with printers through a
standardized protocol. They translate between a printer's
native API and the application interfaces that the app already
uses.
This documentation explains the compatibility boundary, runtime ownership,
connection and status flow, command and feature behavior, built-in and plugin
agent implementations, and the testing evidence required for compatibility
claims.
## What printer agents do
A printer agent has two jobs:
1. Accept the app's existing commands and translate the ones its
printer supports.
2. Convert native printer status into correctly-shaped state that
`MachineObject` understands.
Currently, agents work at a compatibility boundary, i.e., making other vendors compatible with Bambu-shaped code, not a vendor-neutral one.
Some Bambu concepts remain part of the payload and command vocabulary.
End goal is to make the whole command and payload interfaces vendor-neutral.
## Vocabulary
Every chapter reuses these terms. The "Is not" column is the part that
causes confusion when it is left implicit.
| Term | Is | Selected by | Is not |
| --- | --- | --- | --- |
| Agent ID | Which printer agent implementation to use | `printer_agent` on the printer preset; empty is the legacy `bbl`-or-`orca` sentinel | Which printer |
| Printer agent | The live `IPrinterAgent` instance for that ID, created and cached once per ID by `NetworkAgentFactory` | Factory lookup on the agent ID | A connection, and not one object per printer |
| Device ID | One printer inside that implementation | Bind with Access Code for the Moonraker family, where the entered address becomes the ID; Bambu uses its own discovery identity | Which protocol |
| `MachineObject` | The Device tab's view of one selected printer | `DeviceManager::selected_machine`, which stores only an ID | Proof that a printer is reachable |
| Freshness | `is_connected()`, a test over the last-update time | Any reset of the update time, including one no status has followed | Proof that status arrived |
| Status-confirmed readiness | A push-status message has actually been parsed | The first real status message | The same thing as a successful `connect_printer()` |
Earlier drafts used "transport" for the printer agent instance. That term
is retired: the code selects an implementation, not a wire protocol.
## How to use this guide
- [Architecture](architecture.md) describes objects, ownership, lifetimes,
error handling, the feature gate, and compatibility contracts for printer
agents.
- [Connection and status](connection-and-status.md) describes how presets,
machines, access codes, status messages, and commands fit together at
runtime. Unlike Architecture, it follows the sequence of selecting an
agent, connecting, receiving status, and sending commands.
- [Printing](printing.md), [filament synchronization](filament.md), and
[camera support](camera.md) are separate chapters because they contain
per-feature detail rather than because they are universally special:
Printing has its send, preflight, recovery, and start contracts; Filament
covers acquisition, mapping selection, and print-time delivery; Camera
covers the distinct Bambu, Moonraker, and Snapmaker ownership models.
- [Built-in agents](agents.md) describes the Moonraker family and the Qidi,
Snapmaker variants.
- [Python plugin agents](plugin-agents.md) describes the plugin bridge and
lifecycle.
- [Testing and troubleshooting](testing.md) explains automated checks, manual
hardware work, known defects, and the evidence required for compatibility
claims.
- The [capability matrix](reference/capability-matrix.md) is the compact
feature reference. The [manual checklist](reference/manual-checklist.html)
is for a live-printer verification pass.
Treat source code as authoritative when it differs from this guide. In
particular, preserve the compatibility rules called out in each chapter:
they protect stored presets, existing profiles, and the Device tab's
assumptions.

View File

@@ -0,0 +1,205 @@
# Built-in printer agents
*Owns the per-vendor behavior of the built-in agents: what each subclass
changes and what it inherits unchanged. Defers the interface every agent
implements to [Architecture](architecture.md) and
[Python plugin agents](plugin-agents.md).*
This chapter covers the built-in Moonraker family: the general
`MoonrakerPrinterAgent` and the Qidi and Snapmaker variants. Creality
(`CrealityPrintAgent`) is also a member of this family and inherits the base
behavior, but has no section here; see the capability matrix for its
per-feature coverage. They share the same connection and status machinery.
Change the base class only when the behavior is valid for all of them.
Each subclass is thin. `MoonrakerPrinterAgent` holds the HTTP connection,
the WebSocket status subscription, the REST command worker, thumbnail
lookup, the chamber-light heuristic, and the upload-and-start path.
`QidiPrinterAgent` overrides filament discovery and adds multi-color box
mapping; `SnapmakerPrinterAgent` overrides filament discovery and camera
setup; `CrealityPrintAgent` overrides filament refresh. Each derives from
`MoonrakerPrinterAgent` and is `final`, which is why the guard rule below
must be type-based.
## Moonraker family
### Connection and commands
Moonraker-family agents use plain HTTP for the LAN connection. The connection
path deliberately ignores a TLS request because the supported printer stacks
serve Moonraker or a reverse proxy over HTTP. Restoring the caller's TLS
default can send a connection to an unavailable HTTPS endpoint.
Status is a Moonraker WebSocket subscription. Commands use REST. Command
translation happens immediately, but the resulting HTTP work runs through one
agent-owned FIFO worker. Each queued operation captures the current base URL
and API key before it is queued, so a later printer switch does not redirect
an earlier command. Keep this separation: network work on the UI path makes
controls feel stalled, and allowing a queued command to reread connection
state can send it to the wrong printer.
Pause, resume, and cancel use the dedicated Moonraker print endpoints. Do not
replace them with queued `PAUSE`, `RESUME`, or `CANCEL_PRINT` G-code. The
endpoints interrupt the print directly; a G-code command can wait behind the
active print or macro.
The request router accepts the Bambu-shaped JSON used by the native device
tab. Supply object-shaped namespaces such as `print` and `system`. A malformed
but parseable payload with a scalar where the router expects an object can
still fail before the unsupported-command fallback. The supported generic fan
status is the standard `fan` object, which represents the part fan only.
Ordinary part-fan control also works through the legacy `gcode_line` path,
which sends `M106` while `is_enable_np` is false. Auxiliary and chamber fans
are neither reported nor controlled.
Do not add `cfg`, `fun`, `aux`, and `stat` to the Moonraker status payload just
to make it look more complete. Together those fields set `is_enable_np` and
make the UI choose its structured fan and extruder commands instead. The
Moonraker agent does not translate those commands, so working controls become
unsupported no-ops. This is a UI-routing constraint, not a reason to expose
structured fan support.
### Status shown by the native device tab
The agent translates Moonraker status into the Bambu-shaped status payload the
existing Device tab understands. Some fields are necessarily synthetic:
- The virtual SD-card readiness bit and a basic software-version row make the
native UI consider the printer ready. Each pull payload also ensures
`m_push_count` and `m_full_msg_count` are at least one and refreshes
`last_push_time`. Together with the normal-storage state and a placeholder
module version, this satisfies the native `is_info_ready()` and printing
gates. These are compatibility scaffolding, not reports of physical storage
or OTA support.
- Current and total layers are emitted only when `print_stats.info` contains
numeric values. Moonraker may send `null`, and many profiles do not emit the
`SET_PRINT_STATS_INFO` data needed to populate them. Do not turn that gap
into a JSON conversion exception.
- Remaining time is estimated from elapsed print time and virtual-SD progress.
It is omitted below two percent progress because the early estimate is too
unstable. Do not derive an ETA by subtracting Moonraker duration counters:
both are elapsed counters, so their difference is overhead, not remaining
time.
- Temperature readings are available, but nozzle diameter and nozzle type are
not supplied in the status payload. The UI can therefore show an unknown
nozzle. Do not make print submission depend on those missing fields.
### Camera thumbnails and lights
For a running job, the agent asks Moonraker for thumbnails and chooses the
widest usable entry, rather than assuming the first entry is useful. It accepts
both thumbnail path spellings used by Moonraker versions, encodes each path
segment, and caches the result by filename. A failed transient lookup is tried
again only a bounded number of times; a clean response without a thumbnail is
cached as a negative result. The response shape handling is source-derived,
not hardware-verified.
> **Do not perform this HTTP lookup while holding `payload_mutex`.** The
> WebSocket thread builds the status payload under that mutex and the UI
> path also needs it, so a thumbnail timeout taken under the lock would
> stall status delivery or the UI. The lookup still blocks the WebSocket
> thread briefly, so move it to a worker if that becomes measurable.
Chamber-light control searches Moonraker objects for names that look like a
light or a standalone LED, then writes the first matching pin, LED, or macro.
The filter exists to avoid treating unrelated objects, such as a beeper, as a
lamp. It remains a heuristic. The incoming `led_node` is validated, but only
`chamber_light` is acted on; `chamber_light2` is deliberately ignored. A
printer with more than one lamp therefore has no reliable node-to-object map.
### Common maintenance limits
The same cache is reused for a selected agent ID, not per physical printer.
Qidi and Snapmaker inherit this behavior. A stateful feature added
to the base class must be reset carefully when a preset switches hosts.
> **Keep guards for this family type-based** - check whether an agent
> derives from `MoonrakerPrinterAgent` rather than comparing its ID to
> `moonraker`. An ID-based guard silently excludes Qidi, Snapmaker, and
> Creality, even though they share the base behavior.
The family has no generic implementation for firmware-specific AMS write
commands. Keep unsupported commands unsupported until the printer-side macro
or API is known. Reporting success for an untranslated command makes the
native UI claim that an action happened when it did not.
## Qidi
Qidi inherits the Moonraker connection, status, camera, and local-print path.
Its differences are Qidi filament discovery and the pre-print multi-color-box
mapping.
### Filament discovery
Discovery first reads the printer's device information to infer a Qidi series
identifier, then falls back to the configured Orca model if needed. Series
inference intentionally recognizes only a narrow set of known names. An
unknown model still produces usable generic filament data, but not a
series-specific preset identifier.
The agent reads a Qidi filament dictionary and the `save_variables` plus
slot-runout data. Failing to fetch the dictionary is non-fatal: slot discovery
continues with fallback material and colour values. Failing to fetch or parse
slot data is fatal to the refresh. A missing runout value means the agent
cannot prove filament is loaded, so it reports that slot as empty. This is an
ambiguity in the firmware data, not proof that the box is empty.
`save_variables.variables` must be an object. Qidi firmware can return `null`
there, and generic JSON value access can throw on a present null. The parser
rejects that shape without throwing. Preserve the null-slot tests whenever the
response parser changes.
### Multi-color mapping before a print
Before every Qidi print-start wrapper, the agent writes `enable_box` and, for
mapped tools, persistent `value_t<tool>` variables. These writes survive the
job. Invalid mapping JSON is checked only after `enable_box` has been written.
When the mapping is enabled, that failure can therefore leave `enable_box=1`.
There is no rollback for this or for a later per-tool write failure, so a
partial mapping can remain on the printer. An empty mapping is accepted when
the box is enabled. Single-colour jobs disable the box but leave old per-tool
assignments in place.
`enable_box` currently follows `task_use_ams`. That meaning has not been
verified against all Qidi firmware: if firmware treats it as "a box exists"
rather than "use the box for this job", this gate is wrong and needs hardware
evidence before it changes.
Only `start_local_print` reaches Moonraker's real upload-and-start path. The
other Qidi mapping wrappers currently return success stubs after applying the
mapping. Do not describe those wrappers as confirmed print paths.
Because the agent cache is keyed by agent type, a Qidi mapping can also become
stale when switching between Qidi printers. This is a generic Moonraker-family
state risk, made more consequential by Qidi's persistent firmware variables.
The configured `printer_type` can also be stale, so treat it as a fallback
hint rather than device truth.
## Snapmaker
Snapmaker uses the Moonraker base and overrides filament discovery and camera
setup. Neither path is hardware-verified in the current documentation set.
Filament information comes from parallel arrays in `print_task_config`.
`filament_exist` defines the number of slots; shorter type, subtype, colour,
vendor, or NFC arrays use safe fallback values. The agent first tries a visible
vendor, type, and colour preset, then a visible type match, and finally a
generic identifier when no preset bundle is available. An empty reported type
is changed to `PLA`, so an unknown occupied spool can look like confirmed PLA.
An unrecognized type can also reach the visible-preset fallback and be paired
with an unrelated visible preset. Treat the resulting preset as a suggestion,
not printer-ground truth.
Snapmaker U1 camera support starts the printer's monitor RPC, then serves the
still JPEG through a small local HTML page that reloads it after each load or
error. The wrapper is required because a direct still-image URL looks frozen.
The RPC is sent from a detached thread so the UI timer does not block on socket
I/O. That thread captures `this` directly, so agent destruction can race with
the camera command. Do not widen this pattern. Route future asynchronous work
through owned lifetime-managed work where possible.
## Source locations
- `src/slic3r/Utils/MoonrakerPrinterAgent.cpp`
- `src/slic3r/Utils/QidiPrinterAgent.cpp`
- `src/slic3r/Utils/SnapmakerPrinterAgent.cpp`

View File

@@ -0,0 +1,158 @@
# Architecture
*Owns the structural rules: what the objects are, who owns them, what an
agent must implement, and which behaviors are compatibility contracts.
Defers the runtime sequence - selecting, connecting, receiving status,
sending commands - to [Connection and status](connection-and-status.md).*
## The compatibility boundary
The Device tab was built around Bambu-style commands and status. A printer
agent is the translation boundary between that existing contract and a
vendor's native protocol:
```text
Device tab <-> MachineObject <-> NetworkAgent <-> IPrinterAgent
<-> vendor protocol
```
Note: end goal is to move beyond this and achieve a truly vendor-neutral translation layer.
The GUI builds commands and reads `MachineObject` state. An agent owns the
vendor request, response, connection, and status translation. Keep vendor
details on the agent side of this boundary.
Status translation is deliberately Bambu-shaped. Agents deliver payloads
through the callbacks used by the existing Bambu path, and
`MachineObject::parse_json()` interprets them. This preserves the Device
tab's established behavior, but it is not a vendor-neutral protocol.
Important (again): end goal is to move beyond this and achieve a truly vendor-neutral translation layer.
## Runtime objects and ownership
`NetworkAgent` is the facade used by the application. It holds one live
`IPrinterAgent` pointer, which is initially null and may return to null
when a selected ID is unavailable. Callers must handle the null case. An
absent agent is an inert state, not permission to fall back to another
printer agent. A fallback would connect to a different implementation than
the one selected by the preset, and could therefore send commands or status
work to the wrong printer.
`NetworkAgentFactory` registers built-in and plugin implementations by
agent ID. It creates and caches one implementation for each ID. The ID
selects a printer agent implementation, while a `MachineObject` selects one
printer by device ID. The resulting cardinality is one active agent to many
machines.
For example, suppose two Moonraker printers are on the LAN at
`192.168.1.20` and `192.168.1.21`. In the Device tab machine-select popup, the
user chooses **Bind with Access Code**; `PinCodePanel::on_mouse_left_up` opens
`InputIpAddressDialog`, and each entered address is bound as a separate
printer. Both presets store the same agent ID, `moonraker`, so
`NetworkAgentFactory::create_printer_agent_by_id` returns the same cached
`IPrinterAgent` pointer for both presets. Each printer nevertheless has its
own `MachineObject` and device ID. For the Moonraker family,
`MoonrakerPrinterAgent::bind_detect` calls `init_device_info` with the entered
address as both the device ID and address, so the two device IDs are the two
addresses.
That is what one active agent to many machines means. Per-printer state must
be keyed by device ID rather than held only on the agent instance, because one
agent object is shared by both printers. State stored only on that object
would be shared between two different machines and could route status or
commands to the wrong one. The same sharing explains why
`GUI_App::switch_printer_agent` compares device IDs even when the agent pointer
is unchanged: otherwise its unchanged-agent early return would skip
reselection when the user switches between these presets, leaving status and
filament work aimed at the previous printer.
> **Do not make an agent instance per printer just to hold device state.**
> Keep per-printer state keyed by device ID, because one agent object is
> shared by every printer of that type - state held on the instance would
> route status or commands to the wrong `MachineObject`.
> **Do not fall back to another printer agent when the live one is null.**
> An absent agent is an inert state. A fallback would connect to a
> different implementation than the preset selected.
## Commands and unsupported work
An agent must either translate a Device-tab command or return an explicit
error. `ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED` means no translation exists.
`ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE` means a translation exists but this
printer cannot use it. `MachineObject::publish_json()` turns either result
into the user-visible unsupported-command response.
Every Device-tab command must leave by one of these four exits. The fifth
path is the one to watch for in review:
```mermaid
flowchart TD
CMD["Device-tab command JSON"] --> PUSH{"pushing namespace?"}
PUSH -- yes --> OK1["Accept - the status stream already satisfies it"]
PUSH -- no --> TRANS{"Translation exists for this agent?"}
TRANS -- no --> E1["Return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED"]
TRANS -- yes --> CAP{"This printer can use it?"}
CAP -- no --> E2["Return ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE"]
CAP -- yes --> OK2["Translate and send to the printer"]
TRANS -. FORBIDDEN .-> BAD["Return success without translating"]
E1 --> PUB["MachineObject::publish_json turns both errors<br/>into the unsupported-command response"]
E2 --> PUB
BAD --> LIE["UI reports an action that never happened"]
```
> **Do not return success for an unhandled command.** That makes an
> unsupported button look as though it worked and hides missing coverage
> from both users and maintainers.
The `pushing` command namespace is the exception. Its request means
"send status"; an active status stream already satisfies it. The Device
Manager sends these requests repeatedly as a keepalive, so rejecting them
would surface a warning repeatedly even though no action is missing.
## Feature gate
`use_printer_agents` enables printer-agent routing. With the gate off,
agent code must have no observable effect. Released profiles can already
contain `printer_agent` values, so activating an agent while the gate is
off would change existing user behavior merely by loading a profile.
Keep the gate at the routing call sites. Do not fold it into general Bambu
vendor checks: slicing and hardware decisions such as AMS, lidar, bed
types, and G-code flavor still describe printer capabilities, not the
selected printer agent.
## Backward compatibility
`printer_agent` remains a `coString`, even when the ID is currently
unregistered. A preset may refer to an optional plugin that is not
installed. The unknown string must load, remain unchanged, and round-trip
without making the preset dirty. The UI may show it as missing, but must
not rewrite it to a fallback ID.
Keep the feature gate's off-path behavior unchanged, preserve stored agent
IDs, and treat Bambu-shaped payloads as a compatibility contract.
The reason these three are grouped is that each looks like a local code
change and is not. Switching which printer agent handles a preset edits no
profile and no project file, so it reads in review as contained to the
agent layer. But a user's stored presets and `.3mf` projects already carry
`printer_agent` values and were saved against the Bambu-shaped payload. So
a change that is local in the code is not local in effect: it reaches
every previously saved file. That is why the gate must be inert when off,
an unknown ID must survive untouched, and the payload shape is treated as
a contract rather than an implementation detail.
## Threading rule
Agents may perform network work on their own threads, but all mutations of
Device Manager maps and `MachineObject` UI state must run on the UI thread.
Queue incoming status before it reaches `parse_json()` or any operation
that adds, removes, selects, or changes a device. This prevents races
between background network callbacks and UI reads. For example, when a status
callback arrives on an agent's network thread, queue it to the UI thread
before it reaches `MachineObject::parse_json()` or changes a device map or
selection. The Device tab reads those same structures on the UI thread, so
parsing or adding, removing, or selecting a device from the network thread
could race with that read.

View File

@@ -0,0 +1,96 @@
# Camera support
*Owns the three camera ownership models and what each one renders through.
Defers the Snapmaker filament path to [Built-in agents](agents.md), even
though the same subclass owns both.*
Camera support has three ownership models. They share the Device panel,
but not a common frame or stream interface.
Two render surfaces, never one:
```mermaid
flowchart LR
BU["Bambu URL provider<br/>LAN or cloud, not from IPrinterAgent"]
MU["Moonraker webcam discovery<br/>/server/webcams/list stream URL"]
SU["Snapmaker camera page<br/>local HTML that polls monitor.jpg"]
BV["wxMediaCtrl2<br/>native Bambu media pipeline"]
WV["Device-panel wxWebView<br/>stream URL or local polling page"]
BU --> BV
MU --> WV
SU -- overrides normal discovery --> WV
```
## Bambu
Bambu playback uses `wxMediaCtrl2` and the native Bambu media pipeline.
The URL comes from the Bambu LAN or cloud path, not from
`IPrinterAgent`. A printer agent should not attempt to force a Bambu URL
through the Moonraker or WebView path.
## Moonraker live view
On connection, the Moonraker agent obtains the first enabled webcam URL
from `/server/webcams/list`. An absolute HTTP URL is used directly. A
relative URL is resolved against the printer's host web root, with the
Moonraker API port removed. This is necessary because a relative webcam
path may exist on the printer's web server but not on the API port.
The connection generation guards the result. A late request must not
replace the URL after the user has selected a different printer. Failed
discovery clears the URL, which prevents a prior camera from remaining
visible on a printer with no camera.
The agent places the discovered URL in its status payload. The Device
panel renders it in `wxWebView`. It reloads only when the URL changes and
resets the camera-start timestamp at that point. Reloading every update
would loop indefinitely for endpoints that redirect, so an unchanged URL
is shown again without calling `LoadURL()`.
The trade-off is intentional: a WebView that loses an unchanged stream
does not automatically reload. Camera controls beyond live viewing remain
out of scope for Moonraker. Recording, timelapse, settings, and virtual
camera are Bambu-oriented features and must not be presented as supported
merely because live view works.
## Snapmaker polling view
Snapmaker overrides normal webcam discovery. It writes a per-printer local
HTML page that polls the printer's `monitor.jpg` with a cache-busting URL.
Each next request starts after the prior image loads or fails, preventing
requests from piling up on a slow printer. A raw snapshot URL is not used,
because it would display one frozen frame instead of a live-looking view.
The printer must be asked to start its camera capture task. While the
camera view is visible, the Device panel requests this at first display
and then attempts another request every 300 seconds. Other agents reject
the command quietly, so the common timer does not create an error for
Bambu or ordinary Moonraker.
The Snapmaker command is sent from a detached thread because the request
can block on socket I/O and the printer responds over a different channel.
This avoids blocking the UI but leaves a raw-`this` lifetime risk: the
agent can be destroyed while the detached operation still refers to it.
Do not extend this path without addressing that ownership boundary.
The wrapper is written below the application cache with a name derived from
the printer IP. The source contains no cleanup path for those files, so they
can accumulate as different printer IPs are used. This is source-derived and
was not reproduced during this rewrite.
Source code proves 300-second renewal attempts only. The long-running
behavior of the shipped polling and renewal cycle has not yet been tested.
Do not claim that the attempt renews an active capture task or that it
prevents camera expiry until hardware verification establishes both.
## Maintenance checklist
- Keep the three ownership models separate.
- Preserve host-root resolution for relative Moonraker URLs.
- Keep generation guards and stale-URL clearing on every discovery path.
- Reload WebView content only after a URL change.
- Reset the camera-start timestamp when the camera URL changes.
- Treat Moonraker as live-view-only and Snapmaker lifecycle behavior as
not yet verified beyond the observed renewal attempts.

View File

@@ -0,0 +1,248 @@
# Connection and status
*Owns the runtime sequence in order: selecting an agent and machine,
starting a connection, receiving status, sending commands. Defers the
structural rules those steps must obey - ownership, no-fallback, unknown
IDs, threading - to [Architecture](architecture.md), and cites them at the
point where they bite.*
## The four runtime concepts
Keep these concepts separate when tracing a connection problem:
- A preset stores an agent ID and printer address.
- `NetworkAgent` holds the active printer agent for that agent ID.
- `MachineObject` represents the selected printer at that address.
- Freshness and status-confirmed readiness are separate states.
An agent ID selects a printer agent implementation. A device ID selects one
printer within that implementation.
A non-Bambu printer reaches the machine list through **Bind with Access
Code**, the tile in the Device tab's machine-select popup. The user enters
an address and an access code, `bind_detect()` probes the address before
any connect, and `DeviceManager::insert_local_device()` creates the
`MachineObject`. For the Moonraker family the address itself becomes the
device ID: `MoonrakerPrinterAgent::bind_detect()` seeds `dev_name` and
`dev_id` from the entered address, so an unreachable or unnamed printer
still shows up as its IP rather than blank.
Binding is the only route for that family. `MoonrakerPrinterAgent::start_discovery()`
deliberately announces nothing, because a partial discovery implementation
would populate the machine list with stale hosts. Bambu is the exception:
it has its own discovery identity and does not use the address as an ID.
`DeviceManager::selected_machine` is only a selected ID. It can name no
resolvable object. `get_selected_machine()` answers whether an object is
actually available. `set_selected_machine()` accepting an ID therefore
does not prove the printer is connected. The selected ID can remain when its
object is unavailable, so connection state must come from the object itself.
## Selecting the agent and machine
`GUI_App::switch_printer_agent()` reads the edited printer preset and
resolves its stored agent ID through `NetworkAgentFactory`.
1. An empty stored ID is a legacy sentinel. It resolves to `bbl` for a
Bambu vendor preset and to `orca` otherwise.
2. If that effective ID is registered, the factory provides the matching
printer agent implementation.
3. Clear the live printer agent only when a nonempty stored ID is unregistered
or the factory cannot construct the matching registered agent.
4. When the active printer agent changes, clear the current selection, user
selection, stale device discoveries, sidebar state, and AMS state before
installing the replacement.
5. Select the preset's address-derived machine for non-Bambu agents.
The lifetimes are easier to see than to read. Note that the agent pointer
can be unchanged while the machine still must be re-selected - that is the
trap in the same-agent path below:
```mermaid
sequenceDiagram
participant U as User
participant P as Printer preset
participant G as GUI_App::switch_printer_agent
participant F as NetworkAgentFactory
participant N as NetworkAgent
participant M as MachineObject
U->>P: Edit or switch the printer preset
P->>G: printer_agent value, possibly empty
G->>G: resolve_printer_agent_id - empty becomes bbl or orca
G->>F: get_printer_agent_info(effective ID)
alt ID not registered, and stored value was nonempty
F-->>G: no info
G->>N: clear the live printer agent
Note over N: null is inert - never fall back to another agent
else ID registered
F->>F: create once per ID, then serve from cache
F-->>G: the cached IPrinterAgent for this ID
alt Agent pointer changed
G->>N: clear selection, user selection, discoveries, sidebar, AMS
G->>N: install the replacement agent
else Same cached pointer returned
Note over G,F: Two presets can share one agent ID
end
G->>M: compare device ID, re-select if the address differs
end
```
> **Do not use the first available machine as a fallback** (rule owned by
> [Architecture](architecture.md), Runtime objects and ownership). It
> connects to a printer the user did not choose, including one owned by a
> different printer agent.
The same-agent path is important too. Two presets can use one agent type
but point at different addresses, and the factory caches one agent per ID,
so switching between them returns the same pointer and would otherwise
skip reselection entirely. Re-select the machine whenever the preset's
address changes, even when the factory returned the same active agent.
Otherwise status and filament work can continue against the previous
printer.
Note: this is a legacy coupling, not the primary workflow. It reads an
address stored on the printer preset itself (`print_host` and
`printhost_port`, named here only so the keys can be found in the code)
and derives a device ID from it with `dev_id_from_address()`. Those keys
predate printer agents and are edited through `PhysicalPrinterDialog`,
which despite its name writes the printer preset rather than a
`PhysicalPrinter` object - that object is no longer constructed. Printers
normally arrive through Bind with Access Code instead, which does not
touch the preset. Both routes end at `insert_local_device()`, so they must
agree on the device ID: `dev_id_from_address()` strips the URL scheme and
drops an empty port, while the bind path stores the address as the user
typed it.
The unknown-`coString` compatibility rule belongs to `architecture.md` under
Backward compatibility. Keep a nonempty unknown `printer_agent` ID unchanged
and display a missing state if needed; do not rewrite it during plugin unload
or choose an arbitrary replacement, so the preset can round-trip while its
plugin is temporarily unavailable.
## Starting a connection
Machine selection causes `MachineObject::connect()` to invoke the active
agent's `connect_printer()` with the selected address and effective access
code. A success return means that the connection attempt started. It does
not mean that the printer is ready or that a status stream is alive.
Moonraker-family agents must force HTTP. Moonraker and print-host
installations commonly serve plain HTTP, while the generic machine path
can request TLS by default. Passing that default through turns a valid
connection into an HTTPS request the printer will refuse. The agent therefore
must keep the connection on HTTP unless its protocol support changes
deliberately and is verified.
## Access codes: four coordinated slots
One effective access code can live in four places:
| Slot | Location | Purpose |
| --- | --- | --- |
| Device runtime | `MachineObject::access_code` | Code learned from the device. |
| User runtime | `MachineObject::user_access_code` | Code entered by the user. |
| Device config | `access_code[dev_id]` | Persisted device value. |
| User config | `user_access_code[dev_id]` | Persisted user value. |
The effective code prefers the user value when present, then the device
value. Keep user input in the user path and device replies in the device
path. Crossing those paths obscures which value should win.
`set_access_code()` deliberately does not save configuration immediately.
Device replies and polls can update it often; forcing a full config write
for each message adds unnecessary work. The normal deferred config save
persists dirty state later. Do not add an eager save just to make this one
path symmetric: device replies and polls update it often, so a config
write per message is wasted work.
> **Do not erase the user access code when a printer connects.** On the
> LAN reselection path that code can be the only credential that lets the
> machine pass the access check and receive the status or access-code
> reply that would refresh it, so erasing it at connection time can leave
> the machine permanently unable to receive updates. A failed connection
> is the place to handle a proven bad credential.
## Receiving status
An agent receives native status, translates it to the existing payload
shape, and dispatches it to the matching `MachineObject`. The object
parses the payload and records when it last received an update.
Readiness is four states, and three of them look connected:
```mermaid
stateDiagram-v2
[*] --> SelectedIdOnly
SelectedIdOnly: Selected ID only
SelectedIdOnly: selected_machine names no resolvable object
SelectedIdOnly --> FreshWindow: connect_printer returns success
FreshWindow: Fresh window
FreshWindow: reset seeded the update time - no status yet
FreshWindow --> Connecting: still fresh, still no push-status
Connecting: Connecting
Connecting: is_connecting true - the honest state
Connecting --> Ready: first push-status message parsed
Ready: Status-confirmed ready
Ready: the only state that proves a usable printer
Ready --> Stale: update time ages out
Stale: Stale
Stale: is_connected false
Stale --> Ready: a later status message arrives
note right of FreshWindow
is_connected() is true from here on.
It is a freshness test over the update
time - not proof that any status arrived.
end note
```
`is_connected()` is a freshness test over the update time. It does not
describe whether `connect_printer()` returned success or whether any status
message arrived: reset initializes the update time, creating an initial
freshness window. `is_connecting()` distinguishes that window from
status-confirmed readiness: while the object is fresh and no push-status
message has arrived, it remains connecting.
> **Do not treat freshness or a successful connect as proof of readiness.**
> Code that needs a usable printer must wait for status-confirmed
> readiness, because the fresh window exists before any status has been
> parsed.
The UI-thread mutation rule belongs to `architecture.md` under Threading rule.
Dispatch the status callback to the UI thread before changing device maps,
selection, or `MachineObject` state, because network callbacks may run in a
worker thread and mutating these structures there races with the Device tab.
## Sending commands
`MachineObject` builds the established command JSON and sends it through
the active `NetworkAgent`. The agent translates it or returns an explicit
unsupported result. It must not report success when no translation exists.
The `pushing` command exception belongs to `architecture.md` under Commands
and unsupported work. It asks for status, and a working status stream already
supplies it, so accepting it avoids false unsupported warnings from the Device
Manager's repeated keepalive.
## Maintainer constraints
- Preserve same-agent reselection by address, because an agent type can
serve more than one printer.
- Preserve the null-agent, no-fallback, and unknown-`coString` rules in
`architecture.md`; selection must remain an explicit user or preset choice,
and stale state must not belong to a replacement printer agent.
- An empty value is the legacy Bambu-or-Orca sentinel, not a missing printer
agent.
- Preserve deferred access-code saves and the no-on-connect-erase rule;
they prevent excessive config writes and credential-driven status loss.
- Keep Moonraker connections HTTP-only unless the agent's protocol support
changes deliberately and is verified.
- Do not treat freshness as proof that status arrived; wait for
status-confirmed readiness. The UI-thread mutation rule is in
`architecture.md` under Threading rule.

View File

@@ -0,0 +1,110 @@
# Filament synchronization and mapping
*Owns the three filament stages end to end: acquiring printer state,
selecting a mapping, and delivering it at print time. Defers the upload
and start mechanics to [Printing](printing.md), and per-vendor discovery
detail to [Built-in agents](agents.md).*
Filament support has three separate stages. A successful first stage does
not mean that a selected mapping will be delivered to the printer.
Stage 3 is where the two paths diverge, and only one of them reaches the
printer:
```mermaid
flowchart TD
SRC["Filament source<br/>Moonraker lane_data, or the classic MMU object"]
ACQ["1 - Acquire state<br/>Moonraker pull, not subscription"]
ST["DevFilaSystem<br/>Bambu-shaped view, synthetic 4-slot groups"]
SEL["2 - Select mapping<br/>Send dialog matches project filament to slots"]
PAR["PrintParams<br/>ams_mapping fields"]
QIDI["Qidi agent writes enable_box<br/>and value_t per tool, then starts the print"]
BASE["Base Moonraker, Happy Hare, AFC<br/>upload and start ignore the mapping fields"]
OK["Mapping reaches the printer"]
DEAD["Mapping never reaches the printer<br/>the job runs on the printer's own loaded-lane behavior"]
SRC --> ACQ --> ST --> SEL --> PAR
PAR -- Qidi --> QIDI --> OK
PAR -- every other target --> BASE --> DEAD
```
## 1. Acquire printer state
`FilamentSyncMode` declares how the UI obtains filament state:
| Mode | Meaning |
| --- | --- |
| `subscription` | A status stream keeps the state current. |
| `pull` | The UI must request state before it can use it. |
| `none` | The agent has no usable filament state. |
Moonraker uses `pull`. Its ordinary status stream does not supply the
filament data used by this UI. In particular, `lane_data` is a Moonraker
database namespace, not a printer object that the existing subscription
can follow. Changing Moonraker to `subscription` would suppress the pull
that actually populates the UI.
The agent first reads `lane_data`, which can describe AFC and newer Happy
Hare installations. If that is unavailable, it reads the classic Happy
Hare `mmu` object. Those response shapes are source-supported but not yet
verified against current Happy Hare and AFC installations.
The current parser expects lane values as strings and silently skips
numeric values. Whether current AFC or Happy Hare installations emit
numeric lane values is unverified.
The received lanes are converted into a Bambu-shaped model so existing
AMS UI can render them. The model groups numeric lane indexes into
synthetic groups of four slots and passes the result through
`ParseV1_0`. This is a UI compatibility adapter, not evidence that the
printer has a Bambu AMS.
Pull state can be stale. The Send dialog can build a mapping from the
current `DevFilaSystem` without refreshing it first, and a failed pull can
leave older state visible. Do not represent a displayed lane list as a
fresh printer read unless the call site just performed the pull.
## 2. Select a mapping
The Send dialog matches each project filament to a compatible reported
slot. It rejects a mismatched material type and then prefers compatible
slots according to the existing mapping rules. The result is carried in
the legacy linear mapping, the explicit AMS-and-slot mapping, and mapping
metadata for the job.
Treat lane numbers as printer contracts. A numeric lane index is used as a
slot index in the synthetic four-slot view, so an incorrect numbering
assumption can select the wrong physical lane.
The material identity code also retains a defect: ABS and ASA can be
shown as PLA when profile identifiers collide. This is not fixed here,
and multi-color mapping has not received hardware verification.
## 3. Deliver the mapping at print time
`PrintJob` copies the selected mappings into `PrintParams`, but base
Moonraker does not read those fields when it uploads and starts a print.
For plain Moonraker, Happy Hare, and AFC targets using that base path, a
correct-looking mapping in the UI is therefore not delivered to the
printer. The print runs using the printer's own loaded-lane behavior.
Qidi is the implemented exception. Its agent writes its own box mapping
before starting the print. That is a Qidi-specific delivery contract, not
a generic Moonraker solution.
There are deliberately no guessed Happy Hare or AFC write macros. Their
macro and variable names are defined by printer-side configuration, so a
guessed command could silently do nothing or control the wrong setup. Add
a delivery path only after verifying the exact contract against upstream
documentation or a real printer.
## Maintenance checklist
- Keep Moonraker in `pull` mode while `lane_data` remains pull-only.
- Refresh or clearly surface stale state before relying on Send-dialog
mappings.
- Do not claim base Moonraker honors mappings until it consumes them at
print time.
- Preserve Qidi as a distinct delivery implementation.
- Verify lane numbering, material identity, and multi-color behavior on
hardware before expanding the mapping contract.

View File

@@ -0,0 +1,161 @@
# Python printer-agent plugins
*Owns the plugin bridge: the implementation contract, registration and
lifetime, and the audit scope. Defers what the agent must do once live to
[Architecture](architecture.md) and
[Connection and status](connection-and-status.md).*
Python printer agents use the current capability bridge. They do not use a
separate adapter or a Moonraker-specific plugin path.
## What an agent must implement
Not overriding a member of `IPrinterAgent` has four different
consequences depending on which tier it is in. This is the whole plugin
contract:
| Tier | Members | Consequence of not overriding |
| --- | --- | --- |
| Pure virtual | `connect_printer`, `disconnect_printer`, `send_message_to_printer`, the `start_*` print operations, `start_discovery`, `bind`, `bind_detect`, `unbind`, the callback setters, `set_cloud_agent`, `get_agent_info`, and the rest of the pure surface | Compile error |
| Concrete, succeeds | `start_subscribe`, `stop_subscribe`, `add_subscribe`, `del_subscribe` | Silently returns `BAMBU_NETWORK_SUCCESS` |
| Concrete, declines | `command_ams_refresh_rfid`, `command_ams_calibrate`, `command_ams_select_tray`, `command_start_camera` | Silently returns `ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED` |
| Concrete, inert | `get_filament_sync_mode`, `fetch_filament_info` | Reports `FilamentSyncMode::none` and `false` - no filament capability at all |
The refusal tier is deliberate: those commands carry Bambu-dialect G-code
in their bodies, so the honest default is a refusal that
`MachineObject::publish_json()` turns into a dialog. The success tier is
equally deliberate - a printer whose status already streams needs no
subscription call.
> **Do not assume a missing override quietly inherits useful behavior, and
> do not assume it fails loudly either.** Only the first tier fails at
> compile time. The second silently reports success, the third silently
> declines, and the fourth silently reports no filament capability.
## The plugin contract
A plugin subclasses `printer_agent.PrinterAgentBase`, the Python binding for
`PrinterAgentPluginCapability`. The capability itself is the live native
`IPrinterAgent`; there is no intermediate protocol adapter, because
`PrinterAgentPluginCapability` inherits both `PluginCapabilityInterface`
and `IPrinterAgent` directly.
`get_type()` stays a `PluginCapabilityInterface` method and
`set_cloud_agent()` remains the native host injection point. A plugin must
implement the pure connection, communication, discovery, binding, print,
callback-registration, and filament-refresh operations. The certificate,
bind-ticket, HMS-snapshot, and user-selected-machine members are pure too;
the table above abridges the list.
The only tracked Python printer-agent implementation is the BBL plugin. There
is no Python Moonraker printer agent in the current source tree. Moonraker is
implemented by the built-in C++ class.
## Registration and lifetime
When an enabled plugin advertises a printer-connection capability, the factory
gets its `AgentInfo` and registers a factory under `AgentInfo.id`. This is the
same registry used for built-in agents.
Two similarly named structs are involved, and they are not the same thing.
`AgentInfo` is what the agent says about itself; `PrinterAgentInfo` is the
registry's entry about it:
```mermaid
classDiagram
class AgentInfo {
<<returned by the agent via get_agent_info>>
string id
string name
string version
string description
}
class PrinterAgentInfo {
<<the registry entry>>
string id
string display_name
string plugin_identifier
PrinterAgentFactory factory
}
class PrinterAgentFactory {
<<std::function>>
takes cloud_agent and log_dir
returns shared_ptr~IPrinterAgent~
}
class NetworkAgentFactory {
<<all static>>
register_printer_agent(id, display_name, factory)
create_printer_agent_by_id(id, cloud_agent, log_dir)
clear_printer_agent_cache()
register_python_printer_agent(plugin_key, capability_name)
deregister_python_printer_agent(plugin_key, capability_name)
}
AgentInfo ..> PrinterAgentInfo : id becomes the registry key
PrinterAgentInfo *-- PrinterAgentFactory
NetworkAgentFactory o-- PrinterAgentInfo : one entry per ID
PrinterAgentFactory ..> PrinterAgentPluginCapability : weak reference
```
`plugin_identifier` is empty for built-ins and
`<plugin_key>;<uuid>;<capability_name>` for plugins - that is how the
registry tells the two apart at deregistration time. Built-in IDs are the
constants `ORCA_PRINTER_AGENT_ID` and `BBL_PRINTER_AGENT_ID`.
Agent IDs are global. A plugin cannot replace a built-in agent or another
plugin with the same ID. Registry rejection is unconditional. The conflicting
capability is disabled and the user is shown the conflict only when `wxTheApp`
exists and the app is not closing. Re-registering the same plugin capability
is allowed so a reload can replace its factory with the current capability
instance.
The registered factory holds a weak reference to the capability. If the plugin
has already gone away, creation returns null instead of reviving a destroyed
Python object. Callers must treat that as no active printer agent.
On deregistration, the factory removes the registry entry and cached agent,
disconnects a cached agent, and clears the live agent if it has the same ID.
This order prevents `NetworkAgent` from retaining a Python implementation
whose module is about to unload. The current path is UI-thread oriented. Raw
pointer hazards become relevant only if deregistration moves to another thread
without adding synchronization around the GUI-held active-agent handle.
## Device-tab integration
Plugins share the native Device tab with built-in agents. There is no
printer-agent API for adding custom Device-tab panels and no plugin-owned
`MachineObject` to populate directly.
Instead, the plugin supplies the same callbacks as any `IPrinterAgent`. Its
status messages must use the Bambu-shaped payload that `MachineObject` already
parses. If a required field is absent, the shared native UI shows its default
or incomplete state. A custom protocol is acceptable inside the plugin, but
its boundary with the app must perform this translation.
## Python calls, errors, and audit scope
The C++ trampoline acquires the Python GIL, invokes each pure virtual override,
logs a Python exception, and rethrows it. A missing override is a separate
C++ pure-virtual failure, not a logged Python traceback. Python construction
also bypasses the virtual trampoline, so the bridge logs a constructor failure
at the construction boundary.
Plugin-created threads need their own exception handling. An exception raised
there does not cross the C++ trampoline; it reaches Python's thread exception
handling and is recorded through redirected Python standard error.
The audit hook is defense in depth, not a sandbox. Current printer-agent
trampoline calls use loading audit mode. In that mode, normal reads are
allowed, only some file writes are checked against allowed roots, and many
operations are outside the policy, including network access and process
creation. Work that runs outside an active trampoline scope, including a
plugin-created thread, has no attributed plugin context and is allowed by
default. Do not treat this mechanism as permission to run untrusted code.
## Source locations
- `src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp`
- `src/slic3r/plugin/pluginTypes/printerAgent/`
`PrinterAgentPluginCapabilityTrampoline.hpp`
- `src/slic3r/Utils/NetworkAgentFactory.cpp`
- `resources/orca_plugins/BBLPrinterAgentPlugin.py`

View File

@@ -0,0 +1,138 @@
# Printing through printer agents
*Owns the send path: connection choices, preflight, upload, start, and the
two recovery flows. Defers filament mapping delivery to
[Filament synchronization](filament.md), which is a separate contract even
though it is applied at print time.*
This chapter describes the printer-agent send path. It is separate from
the older print-host implementation, even when both target Moonraker.
Keep the paths separate unless their contracts and failure handling can
be deliberately reconciled.
## Connection choices
Three connection paths are in use:
| Target | Connection path | Use |
| --- | --- | --- |
| Native Bambu | Custom TLS tunnel on port 6000 | Send and optional eMMC preflight |
| Bambu Python agent | Implicit FTPS on port 990 | Upload and Bambu preflight fallback |
| Moonraker family | HTTP | Upload and start print |
The Bambu connection paths are independent. Selecting one does not prove that
the other is available. The Moonraker agent uploads with a multipart
request to its `gcodes` storage and then starts the uploaded filename;
it does not reuse the legacy `Moonraker` print-host class.
## Bambu native tunnel
The native tunnel depends on the versioned networking DLL and its
file-transfer module. `InitFTModule()` is a single-owner initialization:
it rejects a second call. Any future shared initialization must therefore
be idempotent, while `BBLNetworkPlugin` remains the single teardown owner.
It must call `UnloadFTModule()` before freeing the DLL, otherwise the
module's function pointers can point into unloaded code.
There is currently an initialization gap: selecting a printer agent does
not initialize this module. It is initialized only when the
`installed_networking` option causes the native BBL network plugin to
initialize. Calls to the tunnel must continue to fail safely until that
path has initialized the module. The Send UI catches this failure and
reports an initialization error instead of letting an exception leave a
wx event handler.
## Bambu FTPS upload
The Python Bambu agent uses implicit FTPS on port 990. Its live upload
path closes the data connection, then waits at most two seconds for the
control response with `voidresp()`. A `TimeoutError` is accepted as a
completed transfer. An `error_reply` is also accepted when its reply
begins with `200`. This is the behavior to preserve.
Do not describe the path as using TLS `unwrap()`: the live construction
does not enable it. Enabling it without a bounded wait could hang while
waiting for the peer's TLS close notification. The current timeout-based
handling has not been verified on hardware against every printer and FTP
server combination.
## Print preflight and recovery
For normal LAN prints, `PrintJob` performs a preflight before the real
send. When eMMC is eligible it tries the native tunnel, then it sends a
small `verify_job` upload through the selected agent. The latter is a real
upload, not a special protocol command. Non-Bambu agents therefore upload
the probe too.
> **Do not re-enable eMMC by default** without hardware coverage for the
> affected devices. It is opt-in because the tunnel can hang during upload
> on some printers.
The whole send, including the thread hop and the recovery fork:
```mermaid
sequenceDiagram
participant UI as Send UI (UI thread)
participant J as PrintJob (worker)
participant A as Selected IPrinterAgent
participant P as Printer
UI->>J: Start send
opt eMMC eligible - off by default
J->>P: Native Bambu tunnel attempt
Note over J,P: Can hang on some printers
end
J->>A: verify_job preflight
Note over J,A: A real upload, not a protocol probe -<br/>non-Bambu agents upload it too
A->>P: Upload probe
alt Preflight and upload succeed
J->>A: Upload the real job
A->>P: Upload, then request print start
Note over A,P: The start response may stay open while<br/>the printer prepares - a timeout is not<br/>proof of failure, so check reported print state
A-->>UI: Result from the reported print state
else Upload fails
J-->>UI: Failure callback, marshalled to the UI thread
Note over UI: Re-resolve the machine here.<br/>Never reuse a machine pointer captured<br/>on the worker - agent or machine may have changed
alt Printer still connected
UI->>UI: Explain that storage upload failed
else Printer disconnected
UI->>UI: Open the IP or access-code flow
end
end
```
An upload failure and a disconnected printer need different recovery:
| Condition | UI response |
| --- | --- |
| Printer is still connected | Explain that storage upload failed. |
| Printer is disconnected | Open the IP or access-code flow. |
> **Do not retain a machine pointer from a worker callback.** The callback
> that chooses between these two outcomes runs on the UI thread and
> re-resolves the machine there, because the selected agent or machine can
> change first. The connection check is adequate for choosing the message,
> but is not a strong enough signal to authorize a reconnect.
## Moonraker upload and start
`MoonrakerPrinterAgent` uploads through Moonraker HTTP, then requests the
print start separately. The start endpoint may keep its response open
while the printer prepares the job. A timeout after that request is not
automatically proof that the start failed: the agent checks the reported
print state before deciding the result.
The legacy print-host Moonraker path implements its own upload and start
logic. It is not the agent path and should not be changed as an implicit
side effect of agent work.
## Maintenance checklist
- Test the selected connection path, not just another path on the same
printer.
- Preserve cancellation and progress callbacks across upload and start.
- Treat `verify_job` as an actual upload when estimating storage effects.
- Keep eMMC opt-in until its hanging behavior is resolved and verified.
- Keep the connected-upload-failure dialog distinct from the disconnected
recovery flow.

View File

@@ -0,0 +1,137 @@
# Printer-agent capability matrix
This is a compact lookup for the built-in Moonraker family. It combines
implementation state with recorded evidence. It is not a promise that every
firmware configuration behaves the same way. Python plug-in behavior depends
on the plug-in, not on this matrix.
Use [testing and troubleshooting](../testing.md) before calling a live-printer
result complete.
For a quicker tour of the controls users actually see, open the
[annotated Device-tab view](device-tab-annotations.html). The annotations
explain the important routing constraints; this matrix remains the compact
cross-agent reference.
## Status definitions
- Supported - implemented, with a relevant live-printer result recorded.
- Partial - an important condition, limitation, or defect applies.
- Unsupported - no applicable implementation, or deliberate refusal.
- Not verified - implemented or source-inspected, but without a relevant live
result.
`Base` means `MoonrakerPrinterAgent`. Qidi, Creality, and Snapmaker inherit
from it unless a row identifies an override.
## Connection and status
| Capability | Base | Qidi | Creality | Snapmaker |
| --- | --- | --- | --- | --- |
| Direct LAN connection with API key | Supported | Supported | Not verified | Not verified |
| WebSocket status updates | Supported | Supported | Not verified | Not verified |
| Reconnect and fresh status | Partial | Partial | Partial | Partial |
| Discovery and cloud binding | Unsupported | Unsupported | Unsupported | Unsupported |
| Device identity with a configured port | Partial | Partial | Partial | Partial |
- Reconnect completion can leave the Device tab with stale status.
- A bare IP and `host:port` can become separate device identities.
- The Base and Qidi Supported grades come from prior hardware sessions. They
were carried into this rewrite and not rerun.
## Controls
| Capability | Base | Qidi | Creality | Snapmaker |
| --- | --- | --- | --- | --- |
| Home and arbitrary G-code | Not verified | Not verified | Not verified | Not verified |
| Bed and nozzle temperature | Not verified | Not verified | Not verified | Not verified |
| Pause, resume, and cancel | Not verified | Not verified | Not verified | Not verified |
| Configured chamber light | Partial | Partial | Partial | Partial |
| Jog and manual extrusion | Partial | Partial | Not verified | Not verified |
| Legacy part-fan speed control | Partial | Partial | Not verified | Not verified |
| Structured fan, chamber, and AI controls | Unsupported | Unsupported | Unsupported | Unsupported |
| AMS RFID, calibration, and tray control | Unsupported | Unsupported | Unsupported | Unsupported |
- Chamber light needs a recognised light object.
- Base and Qidi jog works, but can leave relative positioning active. Do not
use it as a general safe-control test until its G-code state is restored.
- Base and Qidi part-fan control works through legacy `gcode_line` while
`is_enable_np` is false. Adding `cfg`, `fun`, `aux`, and `stat` flips that
flag and routes fan and extruder controls to unsupported structured commands.
- Creality and Snapmaker inherit the source path but have no separate live
evidence for jog or fan control.
## Printing
| Capability | Base | Qidi | Creality | Snapmaker |
| --- | --- | --- | --- | --- |
| Upload G-code without starting | Not verified | Not verified | Not verified | Not verified |
| Upload and start a local print | Not verified | Not verified | Not verified | Not verified |
| Mapped multi-material print | Unsupported | Not verified | Unsupported | Unsupported |
| Cloud or SD-card start variants | Unsupported | Partial | Unsupported | Unsupported |
| Cancel during upload | Not verified | Not verified | Not verified | Not verified |
| Send with no nozzle identity | Not applicable | Not verified | Not applicable | Not applicable |
- Qidi applies mapping before it routes the real local print path.
- Some Qidi print variants can reach base success stubs after mapping.
- Qidi tolerates missing nozzle data in source, but that Send preflight is not
hardware-verified.
## Filament
| Capability | Base | Qidi | Creality | Snapmaker |
| --- | --- | --- | --- | --- |
| Sync mode | Not verified | Not verified | Not verified | Not verified |
| Read installed material and slots | Partial | Not verified | Not verified | Not verified |
| Slot, material, and colour refresh | Unsupported | Not verified | Not verified | Not verified |
| Cleanup after removed material | Not verified | Not verified | Not verified | Not verified |
| Load, unload, or write a slot | Unsupported | Partial | Unsupported | Unsupported |
| Auto Refill | Unsupported | Unsupported | Unsupported | Unsupported |
- All built-in agents use pull-mode sync.
- Base reads Happy Hare or AFC data when present. Qidi has print-time mapping;
Creality has CFS logic; Snapmaker reads printer arrays and NFC data.
- The Base Device-tab slot refresh uses a proprietary AMS command and has no
generic Moonraker translation.
- Generic write-side macros stay unsupported until their printer contract is
known and verified.
## Camera
| Capability | Base | Qidi | Creality | Snapmaker |
| --- | --- | --- | --- | --- |
| Discover a Moonraker webcam | Not verified | Not verified | Not verified | Unsupported |
| Provide a camera source | Not verified | Not verified | Not verified | Not verified |
| Live camera view | Not verified | Not verified | Not verified | Not verified |
| Snapshot-only camera start | Unsupported | Unsupported | Unsupported | Not verified |
| Camera start renewal and teardown | Unsupported | Unsupported | Unsupported | Partial |
| Print thumbnail | Not verified | Not verified | Not verified | Not verified |
- Snapmaker bypasses webcam discovery with a local snapshot-polling page.
- Its renewal and teardown path has lifetime risks without live evidence.
- Moonraker thumbnail endpoint responses and filename-cache behavior need live
coverage.
## Python plug-ins
| Capability | Python plug-in agent |
| --- | --- |
| Registration and re-registration | Not verified - lifecycle tests cover replacement |
| Duplicate agent ID | Not verified - conflict is rejected and reported |
| Disable or unload | Not verified - deregistration is tested; session teardown needs coverage |
| Capability surface | Defined by the plug-in and exposed Python API |
## Reading the matrix safely
- `Partial` is not a softer form of `Supported`. It names a condition that must
be checked before use. `Not verified` means the code was found, but no
relevant live result is recorded.
- Pair each claim with the evidence grades in the testing guide. This matters
especially for CFS, Snapmaker camera, MMU macros, Qidi Send preflight, and
thumbnail endpoints.
## Background - deliberate exclusions
The matrix excludes Bambu-specific cloud binding, RFID, calibration, and
camera-control features from the Moonraker family. They use different protocol
contracts and are deliberately refused when no safe Klipper equivalent exists.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,280 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Printer-agent manual checklist</title>
<style>
:root { color-scheme: light dark; --bg:#111827; --panel:#1f2937; --line:#4b5563;
--text:#f9fafb; --muted:#cbd5e1; --pass:#34d399; --fail:#f87171; --block:#fbbf24; }
* { box-sizing:border-box; }
body { max-width:960px; margin:0 auto; padding:24px; font:15px/1.5 system-ui,sans-serif;
color:var(--text); background:var(--bg); }
h1 { margin:0 0 4px; } h2 { margin-top:32px; } p, li { max-width:78ch; }
.muted { color:var(--muted); } .meta { display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
gap:12px; margin:20px 0; } label { display:grid; gap:4px; }
input, textarea, button { font:inherit; } input, textarea { width:100%; padding:8px;
border:1px solid var(--line); border-radius:6px; color:inherit; background:var(--panel); }
textarea { min-height:58px; margin-top:8px; } .toolbar { display:flex; flex-wrap:wrap;
gap:8px; align-items:center; margin:16px 0; } button { border:1px solid var(--line);
border-radius:6px; padding:6px 10px; cursor:pointer; color:inherit; background:var(--panel); }
button[data-state="pass"].active { color:#062d1d; background:var(--pass); }
button[data-state="fail"].active { color:#3b0808; background:var(--fail); }
button[data-state="blocked"].active { color:#3b2600; background:var(--block); }
.progress { flex:1 1 240px; height:10px; overflow:hidden; border-radius:999px; background:var(--line); }
.progress > div { height:100%; width:0; background:var(--pass); transition:width .15s; }
.case { margin:10px 0; padding:12px; border:1px solid var(--line); border-left:4px solid var(--line);
border-radius:6px; background:var(--panel); } .case.pass { border-left-color:var(--pass); }
.case.fail { border-left-color:var(--fail); } .case.blocked { border-left-color:var(--block); }
.case-head { display:flex; justify-content:space-between; gap:12px; align-items:start; }
.case h3 { margin:0; font-size:1rem; } .actions { display:flex; gap:5px; white-space:nowrap; }
.restore-warning { padding:8px 10px; border:1px solid var(--block); border-radius:6px;
color:var(--block); background:var(--panel); }
code { overflow-wrap:anywhere; } @media (max-width:600px) { body { padding:16px; }
.meta { grid-template-columns:1fr; } .case-head { display:block; } .actions { margin-top:8px; } }
</style>
</head>
<body>
<h1>Printer-agent manual checklist</h1>
<p class="muted">Record what the printer did, not just what the UI displayed. Mark a case
blocked when the required printer, configuration, or safe test condition is unavailable.</p>
<section class="meta" aria-label="Test context">
<label>Agent and printer<input id="target" placeholder="for example: Moonraker - printer model"></label>
<label>Firmware and configuration<input id="environment" placeholder="firmware, MMU, camera, API key setup"></label>
<label>Build or package<input id="build" placeholder="version or build identifier"></label>
<label>Tester and date<input id="tester" placeholder="name and date"></label>
</section>
<div class="toolbar">
<strong id="summary">0 of 0 cases marked</strong>
<div class="progress" aria-label="Checklist progress"><div id="bar"></div></div>
<button id="export" type="button">Export Markdown</button>
<button id="reset" type="button">Reset checklist</button>
</div>
<p id="restore-warning" class="restore-warning" hidden>Saved checklist data could not be
restored. You can export the current blank checklist or use Reset to remove the saved data.</p>
<p class="muted">Use Pass only after observing the expected result. Fail needs enough evidence
to reproduce it. Include response text, log markers, or firmware behavior in the note.</p>
<section>
<h2>Connect and observe status</h2>
<div class="case" data-id="connect">
<div class="case-head"><div><h3>Connect to the selected printer</h3>
<p>Expected: the Device tab receives a fresh status update after connection. Do not use a
successful connection return alone as the result.</p></div><div class="actions"></div></div><textarea placeholder="Evidence, result, or blocker"></textarea></div>
<div class="case" data-id="status">
<div class="case-head"><div><h3>Observe live status changes</h3>
<p>Expected: temperature and target changes, fan state, print state, filename, progress,
elapsed time, and homing state reach the UI while the printer changes state.</p></div><div class="actions"></div></div><textarea placeholder="Evidence, result, or blocker"></textarea></div>
<div class="case" data-id="reconnect">
<div class="case-head"><div><h3>Disconnect and reconnect</h3>
<p>Expected: a second connection produces new status messages and does not create a duplicate
device. Record the post-reconnect status evidence.</p></div><div class="actions"></div></div><textarea placeholder="Evidence, result, or blocker"></textarea></div>
<div class="case" data-id="network-errors">
<div class="case-head"><div><h3>Handle network and response failures</h3>
<p>Expected: for discovery, status, G-code, upload, and print start, exercise controlled
HTTP 401, 404, and 500 responses, invalid JSON, and refused sockets. Each operation must
fail clearly or offer a retry, without a crash or a false success. Record the operation,
injected failure, UI result, and any retry.</p></div><div class="actions"></div></div><textarea placeholder="Operation, injected failure, UI result, retry, and evidence"></textarea></div>
</section>
<section>
<h2>Controls and printing</h2>
<div class="case" data-id="controls">
<div class="case-head"><div><h3>Exercise safe controls</h3>
<p>Expected: home, bed and nozzle temperature, and a harmless G-code command take effect on
the printer. Do not use Moonraker jog as a safe control test while it can leave relative
positioning active.</p></div><div class="actions"></div></div><textarea placeholder="Commands sent and printer-side result"></textarea></div>
<div class="case" data-id="fifo-order">
<div class="case-head"><div><h3>Verify queued command order under latency</h3>
<p>Expected: queue three harmless, uniquely marked commands while a proxy, network shaper,
or request log introduces or records latency. The printer-side log must show the markers in
the same order they were queued. Record the latency method and observed order.</p></div><div class="actions"></div></div><textarea placeholder="Queued markers, latency method, printer-side order, and result"></textarea></div>
<div class="case" data-id="send-only">
<div class="case-head"><div><h3>Send a file without starting it</h3>
<p>Expected: the file appears on the printer and no print starts.</p></div><div class="actions"></div></div><textarea placeholder="Filename and observed result"></textarea></div>
<div class="case" data-id="print">
<div class="case-head"><div><h3>Start a small print</h3>
<p>Expected: upload completes, the printer starts the selected file, and status transitions
to printing.</p></div><div class="actions"></div></div><textarea placeholder="Filename, response, and observed result"></textarea></div>
<div class="case" data-id="active-print-controls">
<div class="case-head"><div><h3>Pause, resume, and cancel an active print</h3>
<p>Expected: after the small print is actively printing, pause it and observe a paused state
on both printer and UI; resume it and observe printing again; then cancel it and observe the
printer stop and the UI leave the active or paused state.</p></div><div class="actions"></div></div><textarea placeholder="State transitions and printer-side result"></textarea></div>
<div class="case" data-id="print-failure">
<div class="case-head"><div><h3>Check upload failure handling</h3>
<p>Expected: cancellation during upload and a missing input fail clearly and do not begin a
partial or unintended print.</p></div><div class="actions"></div></div><textarea placeholder="Failure path and observed result"></textarea></div>
</section>
<section>
<h2>Filament, camera, and agent-specific checks</h2>
<div class="case" data-id="filament-read">
<div class="case-head"><div><h3>Refresh material-system data</h3>
<p>Expected: populated slots, empty slots, material, colour, and a live change are represented
correctly. Moonraker-family agents pull this data; do not expect a subscription callback.</p></div><div class="actions"></div></div><textarea placeholder="MMU or box configuration and observed result"></textarea></div>
<div class="case" data-id="filament-cleanup">
<div class="case-head"><div><h3>Clear material data when filament or the system is absent</h3>
<p>Expected: remove filament or disable the material system, refresh, and confirm the UI no
longer shows obsolete slots, material names, or colours.</p></div><div class="actions"></div></div><textarea placeholder="Change made, refresh evidence, and remaining or cleared data"></textarea></div>
<div class="case" data-id="filament-write">
<div class="case-head"><div><h3>Verify print-time mapping where supported</h3>
<p>Expected: only agents with a documented mapping implementation change printer-side mapping.
Do not attempt load, unload, or slot-setting macros unless their printer-specific contract is
known and safe.</p></div><div class="actions"></div></div><textarea placeholder="Mapping path, printer configuration, and result"></textarea></div>
<div class="case" data-id="qidi-nozzle-preflight">
<div class="case-head"><div><h3>Check Qidi Send preflight with missing nozzle identity</h3>
<p>Expected: on a Qidi agent and compatible single-nozzle slice, Send proceeds when the
Device tab has no reported nozzle diameter or type. It must not stop with
<code>PrintStatusNozzleDataInvalid</code>. Record any reported identity and any mismatch
result separately; this does not approve a known mismatch.</p></div><div class="actions"></div></div><textarea placeholder="Slice, reported nozzle data, preflight result, and printer-side result"></textarea></div>
<div class="case" data-id="camera">
<div class="case-head"><div><h3>Verify a camera feed</h3>
<p>Expected: frames advance and switching printers does not display a stale feed. For Snapmaker,
observe immediately before and after 300 seconds in one open view. The renewal result is
unknown until hardware evidence exists. Then swap agents and shut down the app to exercise
teardown around the detached callback's raw-<code>this</code> lifetime risk.</p></div><div class="actions"></div></div><textarea placeholder="Camera type, timestamps, agent swap or shutdown result, and evidence"></textarea></div>
<div class="case" data-id="thumbnail">
<div class="case-head"><div><h3>Verify the print thumbnail</h3>
<p>Expected: test a reused filename after its thumbnail changes, response payloads with both
<code>thumbnail_path</code> and <code>relative_path</code>, and a path below the G-code
root. The displayed image must match the current file in each case.</p></div><div class="actions"></div></div><textarea placeholder="Filename, endpoint key and path, displayed image, and observed result"></textarea></div>
<div class="case" data-id="plugin">
<div class="case-head"><div><h3>Reload a Python printer-agent plug-in</h3>
<p>Expected: the capability registers once, duplicate agent IDs are rejected visibly, and
disable or unload removes the agent cleanly.</p></div><div class="actions"></div></div><textarea placeholder="Plug-in identifier, actions, and observed result"></textarea></div>
</section>
<script>
const storageKey = 'orca-printer-agent-manual-checklist-v1';
const cases = [...document.querySelectorAll('.case')];
const inputs = [...document.querySelectorAll('input')];
const restoreWarning = document.querySelector('#restore-warning');
function emptyState() {
return { meta: {}, cases: {} };
}
function loadState() {
const saved = localStorage.getItem(storageKey);
if (!saved) return emptyState();
try {
const parsed = JSON.parse(saved);
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object' ||
(parsed.meta !== undefined && (Array.isArray(parsed.meta) || typeof parsed.meta !== 'object')) ||
(parsed.cases !== undefined && (Array.isArray(parsed.cases) || typeof parsed.cases !== 'object'))) {
throw new Error('incompatible saved checklist state');
}
return { meta: parsed.meta || {}, cases: parsed.cases || {} };
} catch (error) {
restoreWarning.hidden = false;
return emptyState();
}
}
const state = loadState();
function caseText(caseElement) {
return caseElement.querySelector('h3').textContent.trim();
}
function caseCriteria(caseElement) {
return [...caseElement.querySelectorAll('.case-head p')]
.map(paragraph => markdownParagraph(paragraph.textContent))
.filter(Boolean)
.join('\n\n');
}
function save() {
const data = { meta: {}, cases: {} };
inputs.forEach(input => { data.meta[input.id] = input.value; });
cases.forEach(item => {
data.cases[item.dataset.id] = { status: item.dataset.status || '', note: item.querySelector('textarea').value };
});
localStorage.setItem(storageKey, JSON.stringify(data));
updateProgress();
}
function renderStatus(item, status) {
item.dataset.status = status;
item.classList.toggle('pass', status === 'pass');
item.classList.toggle('fail', status === 'fail');
item.classList.toggle('blocked', status === 'blocked');
item.querySelectorAll('button[data-state]').forEach(button => {
button.classList.toggle('active', button.dataset.state === status);
});
}
function applyStatus(item, status) {
renderStatus(item, status);
save();
}
function updateProgress() {
const marked = cases.filter(item => item.dataset.status).length;
document.querySelector('#summary').textContent = `${marked} of ${cases.length} cases marked`;
document.querySelector('#bar').style.width = `${cases.length ? marked * 100 / cases.length : 0}%`;
}
function markdown() {
const meta = stateFromInputs();
const lines = ['# Printer-agent manual verification', '',
`Target: ${markdownInline(meta.target) || 'not recorded'}`,
`Firmware and configuration: ${markdownInline(meta.environment) || 'not recorded'}`,
`Build or package: ${markdownInline(meta.build) || 'not recorded'}`,
`Tester and date: ${markdownInline(meta.tester) || 'not recorded'}`, ''];
cases.forEach(item => {
const status = item.dataset.status || 'unmarked';
const note = markdownInline(item.querySelector('textarea').value);
lines.push(`## ${markdownInline(caseText(item))}`, '',
`Status: ${status}`, '',
'### Test criteria and expected result', '',
caseCriteria(item) || 'Not recorded', '',
'### Notes', '', note || 'None', '');
});
return lines.join('\n');
}
function markdownParagraph(value) {
return markdownInline(value);
}
function markdownInline(value) {
return String(value || '').replace(/\r\n?|\n/g, ' ').replace(/[\\`*_{}[\]<>#+!|]/g, '\\$&')
.replace(/\s+/g, ' ').trim();
}
function stateFromInputs() {
return Object.fromEntries(inputs.map(input => [input.id, input.value]));
}
function downloadMarkdown() {
const blob = new Blob([markdown()], { type: 'text/markdown;charset=utf-8' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'printer-agent-manual-verification.md';
link.click();
URL.revokeObjectURL(link.href);
}
cases.forEach(item => {
const actions = item.querySelector('.actions');
['pass', 'fail', 'blocked'].forEach(status => {
const button = document.createElement('button');
button.type = 'button';
button.dataset.state = status;
button.textContent = status[0].toUpperCase() + status.slice(1);
button.addEventListener('click', () => applyStatus(item, status));
actions.appendChild(button);
});
const saved = state.cases[item.dataset.id] || {};
item.querySelector('textarea').value = typeof saved.note === 'string' ? saved.note : '';
if (['pass', 'fail', 'blocked'].includes(saved.status)) renderStatus(item, saved.status);
item.querySelector('textarea').addEventListener('input', save);
});
inputs.forEach(input => {
input.value = typeof state.meta[input.id] === 'string' ? state.meta[input.id] : '';
input.addEventListener('input', save);
});
document.querySelector('#export').addEventListener('click', downloadMarkdown);
document.querySelector('#reset').addEventListener('click', () => {
if (!confirm('Clear all saved checklist data for this browser?')) return;
localStorage.removeItem(storageKey);
location.reload();
});
updateProgress();
</script>
</body>
</html>

View File

@@ -0,0 +1,249 @@
# Testing and troubleshooting printer agents
*Owns evidence grades, the automated and manual verification passes, and
the open-defect register. Every "not hardware-verified" note elsewhere in
this guide resolves to a grade defined here.*
This page describes how to assess a printer-agent change without treating
source inspection as a hardware result. Use the
[manual checklist](reference/manual-checklist.html) for a repeatable live-printer
pass, and use the [capability matrix](reference/capability-matrix.md) to decide
which cases apply to the agent being changed.
## Evidence grades
Keep these grades separate in reviews and release notes.
- Source-inspected - the current implementation was read. It establishes
intended behavior, not printer compatibility.
- Automated - a targeted test ran. It covers its inputs and assertions, not a
printer, firmware version, or network failure that it does not model.
- Hardware-verified - the stated behavior was observed on a named class of
live printer. Record the model, firmware, configuration, and result with the
test evidence.
Do not call a capability supported by hardware solely because the code compiles or
a unit test passes. Conversely, a hardware observation should not be generalized
to every Moonraker-family printer without checking its configuration.
## Carried hardware evidence
Prior hardware sessions verified direct Moonraker-family connection, live
WebSocket status, and jog on both a Qidi/Moonraker printer and a generic
Moonraker box. The jog moved the printer but could leave relative positioning
active. This evidence establishes the Moonraker-base and Qidi grades in the
capability matrix. It was carried into this rewrite and was not rerun here.
It does not establish those behaviors for Creality or Snapmaker, and it does
not cover Qidi-specific filament discovery, box mapping, or print wrappers.
## Build and automated tests
Run the smallest relevant test target first, then broaden the run if the change
crosses shared agent, plug-in, or Device-tab code. Set
`<configured-build-dir>` to the CMake build tree that was already configured for
the compiler, generator, and build type you intend to use. Do not replace it
with the source directory or assume a `build` subdirectory exists.
```powershell
cmake --build <configured-build-dir> --config RelWithDebInfo --target slic3rutils_tests
cmake --build <configured-build-dir> --config RelWithDebInfo --target printer_agent_plugin_tests
ctest --test-dir <configured-build-dir>/tests/libslic3r --output-on-failure
```
`--config RelWithDebInfo` is needed for multi-config generators such as Visual
Studio. Omit it only when the configured generator is single-config and its
build type was selected at configure time. Parallel-build options belong to the
generator: for example, pass `--parallel 6` to CMake when the generator
supports it, or use the generator's own trailing arguments only when that
generator documents them. Do not combine a changed working directory, a
generator-specific flag, and an assumed build-tree layout in one command.
On Windows, start from an MSVC developer environment. A shell without the MSVC
include paths can fail in dependencies before it compiles Orca code, with errors
such as `C1083: Cannot open include file: 'stddef.h'`, `'time.h'`, or `'cstdint'`.
Those signatures are environment failures, not evidence against the agent change.
If a machine exhausts MSVC precompiled-header memory, use the documented lower
parallelism command:
```powershell
cmake --build <configured-build-dir> --config RelWithDebInfo --target slic3rutils_tests --parallel 6
```
Errors such as `C3859: Failed to create virtual memory for PCH` and `C1076:
internal heap limit reached` are machine-specific resource failures. If a build
appears hung and file operations are blocked, inspect for idle `cl.exe` processes
holding locks before changing source.
Relevant automated coverage includes:
- `tests/slic3rutils/test_qidi_printer_agent.cpp` validates malformed and null
Qidi slot responses without throwing.
- `tests/slic3rutils/test_printer_agent.cpp` checks the public printer-agent
surface, including filament-sync mode exposure.
- `tests/slic3rutils/test_printer_agent_plugin.cpp` exercises plug-in
registration, replacement, conflict handling, and deregistration.
Do not present a historic test count, failure count, or skipped-test count as the
current state. Run the command above and attach its own output when a current
result is needed.
## Manual hardware verification
Use a small, disposable model and a printer that can safely accept the actions.
The checklist groups the work in the order below.
1. Confirm the printer accepts its configured URL and API key, then select it in
the Device tab. Verify a fresh status update, not merely a successful connect
return code.
2. Observe temperatures, targets, fan state, print state, filename, progress,
elapsed time, and axis homing while the printer changes state.
3. Exercise safe idle controls first: home, bed and nozzle temperature, and a
harmless G-code command. Verify the printer's action as well as the UI
response.
4. Send a small file without starting it, then start a small print. Once the
print is active, pause it, confirm the printer and UI both enter a paused
state, resume it, and confirm both return to printing. Cancel only after
observing an active or paused print, then confirm that the printer stops and
the UI leaves that state. Cancel an upload and retry a missing input so that
failure handling is observed too.
5. For Moonraker command workers, send three harmless, uniquely marked commands
while a proxy, network shaper, or request log introduces or records latency.
Pass only if the printer-side log records the markers in the same order they
were queued. Record the latency method and the observed order.
6. For a material system, verify populated slots, empty slots, material, colour,
refresh after a change, and cleanup when the system is absent or filament is
removed. The latter must remove obsolete slot or material data from the UI.
Do not infer write support from read support.
7. For Qidi, use a compatible single-nozzle slice and Send it while the Device
tab has no reported nozzle diameter or type. Pass only if Send proceeds past
preflight without `PrintStatusNozzleDataInvalid`; record any reported
diameter/type and any mismatch message separately. This checks the intended
tolerance for missing identity data, not that a mismatched known nozzle is
safe.
8. Verify the camera only on hardware that advertises or implements it. Check
that frames advance, switching printers starts the newly selected camera, and
closing or changing the view does not leave misleading stale output. For
Snapmaker, also test immediately before and after 300 seconds in an
uninterrupted view. The expected renewal result is unknown until hardware
evidence exists. Swap agents and shut down the app after the camera cases to
exercise teardown around the detached callback's raw-`this` lifetime risk.
9. For Moonraker thumbnails, test a reused filename after its thumbnail changes,
responses that use `thumbnail_path` and `relative_path`, and a thumbnail in a
subdirectory below the G-code root. Record the endpoint payload and displayed
image for each case.
10. Disconnect and reconnect the printer, then confirm that new status messages
still reach the UI. A reconnect completion alone is insufficient evidence.
11. Test network and response failures for discovery, status, G-code, upload,
and print start. For each operation, exercise HTTP 401, 404, and 500,
invalid JSON, and a refused socket with a controlled proxy or test server.
Each case must fail clearly or offer a retry, without a crash or a false
success. Record the operation, injected failure, UI result, and any retry.
For Moonraker, record whether the thumbnail endpoint returns the response shape
the agent expects. That response has not yet been verified across a live
Moonraker deployment.
## Troubleshooting by symptom
### Connection appears successful but the Device tab stays stale
Treat status freshness as the connection result. Enable `ORCA_NETWORK_DEBUG` and
look for a new `parse_json: dev_id=` entry after the connection or reconnection.
The unresolved reconnect-delivery problem can complete the second connection
without delivering any new parsed messages. Capture an instrumented second
connection before changing dispatch or message-delay logic, because both remain
plausible causes.
Check identity too. One path can use a bare IP address while another uses
`host:port`; configuring a port can therefore create two machine objects. Do not
diagnose a duplicate as a printer-agent failure until the identities are
compared.
### A control reports success but the printer did not change
First establish that the command has a documented translation in the capability
matrix. Unsupported commands are deliberately rejected rather than silently
accepted. For Moonraker, queued controls are asynchronous, so wait for the
printer-side result and capture the request or log before concluding it was lost.
Moonraker jog is a special case. The current path can leave the printer in
relative positioning mode after a jog. Do not use it as a general verification
control until it is changed to save state, issue `G91` and the move, then restore
state with `SAVE_GCODE_STATE` and `RESTORE_GCODE_STATE`. Extruder-relative moves
use a separate `M83` path.
### A thumbnail is missing or belongs to an earlier print
The thumbnail lookup accepts both `thumbnail_path` and `relative_path`, but the
live endpoint response is not yet verified. The cache is keyed by filename, so
reusing a common name can retain the previous image. Test a distinct filename
before changing the lookup. Paths below the G-code root also need live coverage
for the `relative_path` fallback.
### Filament looks stale, blank, or does not follow an edit
Moonraker-family agents use pull-mode filament sync. Verify the pull request and
the resulting Device-tab update rather than expecting a subscription callback.
Read-side discovery does not establish load, unload, slot-setting, or Auto Refill
support. Happy Hare and AFC macro names are printer-side configuration; do not
guess them. A guessed macro can silently do nothing or issue the wrong action.
### A Python agent disappears or cannot be enabled
Check its agent ID first. A duplicate ID is rejected and the conflicting
capability is disabled rather than auto-promoted later, because automatic
promotion could change the active printer implementation without user intent.
Reload and teardown also need a live check: registration tests cover lifecycle
logic, but a plug-in can still be exposed to API drift or a teardown race in a
real session.
## Known defects and safeguards
- Reconnect delivery remains unresolved. Instrument the second connection before
attempting a fix; the observed failure is stale data after a completed reconnect.
- Moonraker jog can leave relative mode active. Keep the future state-save and
restore sequence together so the jog cannot affect later G-code positioning.
- Qidi's nozzle-data Send-preflight tolerance for unreported diameter and type
has not received a hardware verification.
- A configured `host:port` can coexist with a bare-IP machine identity. This can
duplicate devices and confuse selection.
- Moonraker thumbnail caching can show an old image when a filename is reused.
- Moonraker filament data can be stale, and its pull/read path does not provide
safe generic write-side MMU operations.
- Duplicate plug-in agent IDs are rejected. There is no automatic fallback to a
losing capability after the winner unloads.
- Plug-in implementations can drift from the Python printer-agent API. Treat an
import or interface error as a plug-in compatibility issue until proved otherwise.
- Snapmaker camera callbacks can outlive their view during agent replacement or
shutdown because the detached path retains a raw `this` pointer. Treat a crash
or stale callback during those transitions as a source-derived use-after-free
risk until the lifetime is made explicit.
- Snapmaker writes an IP-specific local camera HTML file below the application
cache. The source contains no cleanup path, so residual files can accumulate
for each unique printer IP. This is source-derived and was not reproduced.
## Not yet hardware-verified
- Moonraker command-worker FIFO behavior under recorded or injected network
latency.
- Moonraker thumbnail responses: reused filenames, `thumbnail_path`,
`relative_path`, and subdirectory paths.
- Snapmaker camera behavior on a live U1: frames, renewal across a long-open
view including the 300-second boundary, switching between printers, agent
replacement, and shutdown teardown.
- Whether Snapmaker's renewal cadence prevents a stale-frame interval. Do not
shorten it as a workaround without resolving the printer-side behavior first.
- Creality CFS detection and preset scoring on a real printer.
- Qidi Send preflight when firmware omits nozzle diameter and type.
- Moonraker-family write-side MMU commands. They remain blocked on verified,
printer-specific macro contracts.
## Background - source locations for maintainers
The Moonraker command worker, status stream, print path, and thumbnail
lookup live in `MoonrakerPrinterAgent`. Qidi maps its material box before routing
to the Moonraker base. Snapmaker adds the camera start request and its snapshot
page. Python agent registration and conflict handling live in
`NetworkAgentFactory`.

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "Custom Printer",
"version": "02.04.00.02",
"version": "02.04.00.01",
"force_update": "0",
"description": "My configurations",
"machine_model_list": [

View File

@@ -24,6 +24,12 @@
"filament_loading_speed_start": [
"50"
],
"filament_multitool_ramming": [
"1"
],
"filament_multitool_ramming_flow": [
"40"
],
"filament_stamping_distance": [
"45"
],

View File

@@ -24,6 +24,12 @@
"filament_loading_speed_start": [
"50"
],
"filament_multitool_ramming": [
"1"
],
"filament_multitool_ramming_flow": [
"40"
],
"filament_stamping_distance": [
"45"
],

View File

@@ -24,6 +24,12 @@
"filament_loading_speed_start": [
"50"
],
"filament_multitool_ramming": [
"1"
],
"filament_multitool_ramming_flow": [
"40"
],
"filament_stamping_distance": [
"45"
],

View File

@@ -24,6 +24,12 @@
"filament_loading_speed_start": [
"50"
],
"filament_multitool_ramming": [
"1"
],
"filament_multitool_ramming_flow": [
"40"
],
"filament_stamping_distance": [
"45"
],

View File

@@ -25,6 +25,12 @@
"filament_loading_speed_start": [
"50"
],
"filament_multitool_ramming": [
"1"
],
"filament_multitool_ramming_flow": [
"40"
],
"filament_stamping_distance": [
"45"
],

View File

@@ -25,6 +25,12 @@
"filament_loading_speed_start": [
"50"
],
"filament_multitool_ramming": [
"1"
],
"filament_multitool_ramming_flow": [
"40"
],
"filament_stamping_distance": [
"45"
],

View File

@@ -25,6 +25,12 @@
"filament_loading_speed_start": [
"50"
],
"filament_multitool_ramming": [
"1"
],
"filament_multitool_ramming_flow": [
"40"
],
"filament_stamping_distance": [
"45"
],

View File

@@ -25,6 +25,12 @@
"filament_loading_speed_start": [
"50"
],
"filament_multitool_ramming": [
"1"
],
"filament_multitool_ramming_flow": [
"40"
],
"filament_stamping_distance": [
"45"
],

View File

@@ -25,6 +25,12 @@
"filament_loading_speed_start": [
"50"
],
"filament_multitool_ramming": [
"1"
],
"filament_multitool_ramming_flow": [
"40"
],
"filament_stamping_distance": [
"45"
],

View File

@@ -116,7 +116,7 @@
"deretraction_speed": [
"30"
],
"z_hop_types": "Slope Lift",
"z_hop_types": "Normal Lift",
"silent_mode": "0",
"single_extruder_multi_material": "1",
"change_filament_gcode": "",

View File

@@ -118,7 +118,7 @@
"deretraction_speed": [
"30"
],
"z_hop_types": "Slope Lift",
"z_hop_types": "Normal Lift",
"silent_mode": "0",
"single_extruder_multi_material": "1",
"change_filament_gcode": "",

View File

@@ -116,7 +116,7 @@
"deretraction_speed": [
"30"
],
"z_hop_types": "Slope Lift",
"z_hop_types": "Normal Lift",
"silent_mode": "0",
"single_extruder_multi_material": "1",
"change_filament_gcode": "",

View File

@@ -172,11 +172,11 @@
"0.4"
],
"z_hop_types": [
"Slope Lift",
"Slope Lift",
"Slope Lift",
"Slope Lift",
"Slope Lift"
"Normal Lift",
"Normal Lift",
"Normal Lift",
"Normal Lift",
"Normal Lift"
],
"purge_in_prime_tower": "0",
"machine_pause_gcode": "M601",

View File

@@ -1,6 +1,6 @@
{
"name": "OrcaFilamentLibrary",
"version": "02.04.00.04",
"version": "02.04.00.03",
"force_update": "0",
"description": "Orca Filament Library",
"filament_list": [

View File

@@ -36,9 +36,6 @@
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"8"
],
"filament_type": [
"PET-CF"
],

View File

@@ -39,9 +39,6 @@
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"8"
],
"filament_vendor": [
"Bambu Lab"
],

View File

@@ -39,9 +39,6 @@
"filament_max_volumetric_speed": [
"6"
],
"filament_multitool_ramming_flow": [
"6"
],
"filament_vendor": [
"Bambu Lab"
],

View File

@@ -21,9 +21,6 @@
"filament_max_volumetric_speed": [
"6"
],
"filament_multitool_ramming_flow": [
"6"
],
"filament_type": [
"PLA-AERO"
],

View File

@@ -27,9 +27,6 @@
"filament_max_volumetric_speed": [
"6"
],
"filament_multitool_ramming_flow": [
"6"
],
"filament_scarf_seam_type": [
"none"
],

View File

@@ -21,9 +21,6 @@
"filament_max_volumetric_speed": [
"6"
],
"filament_multitool_ramming_flow": [
"6"
],
"filament_vendor": [
"Bambu Lab"
],

View File

@@ -36,9 +36,6 @@
"filament_max_volumetric_speed": [
"1"
],
"filament_multitool_ramming_flow": [
"1"
],
"filament_retraction_minimum_travel": [
"3"
],

View File

@@ -72,6 +72,15 @@
"fan_min_speed": [
"10"
],
"filament_cooling_final_speed": [
"0"
],
"filament_cooling_initial_speed": [
"0"
],
"filament_cooling_moves": [
"0"
],
"filament_cost": [
"599"
],
@@ -90,12 +99,30 @@
"filament_is_support": [
"0"
],
"filament_loading_speed": [
"0"
],
"filament_loading_speed_start": [
"0"
],
"filament_long_retractions_when_cut": [
"nil"
],
"filament_max_volumetric_speed": [
"15"
],
"filament_minimal_purge_on_wipe_tower": [
"0"
],
"filament_multitool_ramming": [
"0"
],
"filament_multitool_ramming_flow": [
"10"
],
"filament_multitool_ramming_volume": [
"10"
],
"filament_notes": [
""
],
@@ -138,6 +165,21 @@
"filament_soluble": [
"0"
],
"filament_stamping_distance": [
"0"
],
"filament_stamping_loading_speed": [
"0"
],
"filament_toolchange_delay": [
"0"
],
"filament_unloading_speed": [
"0"
],
"filament_unloading_speed_start": [
"0"
],
"filament_vendor": [
"Elas"
],

View File

@@ -42,6 +42,15 @@
"fan_min_speed": [
"20"
],
"filament_cooling_final_speed": [
"0"
],
"filament_cooling_initial_speed": [
"0"
],
"filament_cooling_moves": [
"0"
],
"filament_cost": [
"445"
],
@@ -60,12 +69,30 @@
"filament_is_support": [
"0"
],
"filament_loading_speed": [
"0"
],
"filament_loading_speed_start": [
"0"
],
"filament_long_retractions_when_cut": [
"1"
],
"filament_max_volumetric_speed": [
"18"
],
"filament_minimal_purge_on_wipe_tower": [
"0"
],
"filament_multitool_ramming": [
"0"
],
"filament_multitool_ramming_flow": [
"10"
],
"filament_multitool_ramming_volume": [
"10"
],
"filament_notes": [
""
],
@@ -108,6 +135,21 @@
"filament_soluble": [
"0"
],
"filament_stamping_distance": [
"0"
],
"filament_stamping_loading_speed": [
"0"
],
"filament_toolchange_delay": [
"0"
],
"filament_unloading_speed": [
"0"
],
"filament_unloading_speed_start": [
"0"
],
"filament_wipe": [
"nil"
],

View File

@@ -71,6 +71,15 @@
"fan_min_speed": [
"60"
],
"filament_cooling_final_speed": [
"0"
],
"filament_cooling_initial_speed": [
"0"
],
"filament_cooling_moves": [
"0"
],
"filament_cost": [
"449.5"
],
@@ -89,12 +98,30 @@
"filament_is_support": [
"0"
],
"filament_loading_speed": [
"0"
],
"filament_loading_speed_start": [
"0"
],
"filament_long_retractions_when_cut": [
"nil"
],
"filament_max_volumetric_speed": [
"25"
],
"filament_minimal_purge_on_wipe_tower": [
"0"
],
"filament_multitool_ramming": [
"0"
],
"filament_multitool_ramming_flow": [
"10"
],
"filament_multitool_ramming_volume": [
"10"
],
"filament_notes": [
""
],
@@ -137,6 +164,21 @@
"filament_soluble": [
"0"
],
"filament_stamping_distance": [
"0"
],
"filament_stamping_loading_speed": [
"0"
],
"filament_toolchange_delay": [
"0"
],
"filament_unloading_speed": [
"0"
],
"filament_unloading_speed_start": [
"0"
],
"filament_vendor": [
"Elas"
],

View File

@@ -71,6 +71,15 @@
"fan_min_speed": [
"100"
],
"filament_cooling_final_speed": [
"0"
],
"filament_cooling_initial_speed": [
"0"
],
"filament_cooling_moves": [
"0"
],
"filament_cost": [
"500"
],
@@ -89,12 +98,30 @@
"filament_is_support": [
"0"
],
"filament_loading_speed": [
"0"
],
"filament_loading_speed_start": [
"0"
],
"filament_long_retractions_when_cut": [
"nil"
],
"filament_max_volumetric_speed": [
"18"
],
"filament_minimal_purge_on_wipe_tower": [
"0"
],
"filament_multitool_ramming": [
"0"
],
"filament_multitool_ramming_flow": [
"10"
],
"filament_multitool_ramming_volume": [
"10"
],
"filament_notes": [
""
],
@@ -137,6 +164,21 @@
"filament_soluble": [
"0"
],
"filament_stamping_distance": [
"0"
],
"filament_stamping_loading_speed": [
"0"
],
"filament_toolchange_delay": [
"0"
],
"filament_unloading_speed": [
"0"
],
"filament_unloading_speed_start": [
"0"
],
"filament_vendor": [
"Elas"
],

View File

@@ -24,9 +24,6 @@
"filament_max_volumetric_speed": [
"6"
],
"filament_multitool_ramming_flow": [
"6"
],
"nozzle_temperature": [
"290"
],

View File

@@ -35,9 +35,6 @@
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"8"
],
"filament_vendor": [
"Elegoo"
],

View File

@@ -12,9 +12,6 @@
"filament_max_volumetric_speed": [
"10"
],
"filament_multitool_ramming_flow": [
"10"
],
"nozzle_temperature": [
"220"
],

View File

@@ -44,8 +44,5 @@
],
"filament_max_volumetric_speed": [
"10"
],
"filament_multitool_ramming_flow": [
"10"
]
}

View File

@@ -44,8 +44,5 @@
],
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"8"
]
}

View File

@@ -10,8 +10,5 @@
"filament_type": [
"PA-CF"
],
"filament_multitool_ramming_flow": [
"8"
],
"compatible_printers": []
}

View File

@@ -8,8 +8,5 @@
"filament_max_volumetric_speed": [
"20"
],
"filament_multitool_ramming_flow": [
"20"
],
"compatible_printers": []
}

View File

@@ -17,9 +17,6 @@
"filament_max_volumetric_speed": [
"11.5"
],
"filament_multitool_ramming_flow": [
"13"
],
"overhang_fan_speed": [
"100"
],

View File

@@ -12,9 +12,6 @@
"filament_max_volumetric_speed": [
"18"
],
"filament_multitool_ramming_flow": [
"25"
],
"slow_down_layer_time": [
"4"
],

View File

@@ -8,9 +8,6 @@
"filament_max_volumetric_speed": [
"11"
],
"filament_multitool_ramming_flow": [
"11"
],
"filament_retraction_length": [
"0.8"
],

View File

@@ -15,9 +15,6 @@
"filament_max_volumetric_speed": [
"6"
],
"filament_multitool_ramming_flow": [
"6"
],
"filament_type": [
"PP-CF"
],

View File

@@ -15,9 +15,6 @@
"filament_max_volumetric_speed": [
"6"
],
"filament_multitool_ramming_flow": [
"6"
],
"filament_type": [
"PP-GF"
],

View File

@@ -17,9 +17,6 @@
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"8"
],
"filament_vendor": [
"Overture"
],

View File

@@ -23,9 +23,6 @@
"filament_max_volumetric_speed": [
"7.5"
],
"filament_multitool_ramming_flow": [
"7.5"
],
"filament_vendor": [
"Valment"
],

View File

@@ -43,12 +43,6 @@
"filament_max_volumetric_speed": [
"12"
],
"filament_multitool_ramming_flow": [
"15"
],
"filament_multitool_ramming_volume": [
"10"
],
"filament_type": [
"ABS"
],

View File

@@ -43,12 +43,6 @@
"filament_max_volumetric_speed": [
"12"
],
"filament_multitool_ramming_flow": [
"12"
],
"filament_multitool_ramming_volume": [
"10"
],
"filament_type": [
"ASA"
],

View File

@@ -41,15 +41,6 @@
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming": [
"0"
],
"filament_multitool_ramming_flow": [
"6"
],
"filament_multitool_ramming_volume": [
"0.1"
],
"filament_type": [
"BVOH"
],

View File

@@ -66,12 +66,6 @@
"filament_minimal_purge_on_wipe_tower": [
"15"
],
"filament_multitool_ramming": [
"1"
],
"filament_multitool_ramming_volume": [
"5"
],
"filament_retract_before_wipe": [
"nil"
],

View File

@@ -41,9 +41,6 @@
"filament_max_volumetric_speed": [
"16"
],
"filament_multitool_ramming_flow": [
"16"
],
"filament_scarf_seam_type": [
"none"
],

View File

@@ -8,9 +8,6 @@
"filament_type": [
"EVA"
],
"filament_multitool_ramming_flow": [
"12"
],
"supertack_plate_temp": [
"0"
],

View File

@@ -41,9 +41,6 @@
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"8"
],
"filament_type": [
"HIPS"
],

View File

@@ -44,9 +44,6 @@
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"12"
],
"filament_type": [
"PA"
],

View File

@@ -7,9 +7,6 @@
"filament_max_volumetric_speed": [
"12"
],
"filament_multitool_ramming_flow": [
"12"
],
"filament_type": [
"PAHT"
],

View File

@@ -86,9 +86,6 @@
"filament_max_volumetric_speed": [
"12"
],
"filament_multitool_ramming_flow": [
"16"
],
"filament_flow_ratio": [
"0.94"
]

View File

@@ -26,9 +26,6 @@
"filament_max_volumetric_speed": [
"12"
],
"filament_multitool_ramming_flow": [
"10"
],
"filament_type": [
"PCTG"
],

View File

@@ -38,9 +38,6 @@
"filament_max_volumetric_speed": [
"12"
],
"filament_multitool_ramming_flow": [
"12"
],
"filament_type": [
"PE"
],

View File

@@ -26,9 +26,6 @@
"filament_max_volumetric_speed": [
"10"
],
"filament_multitool_ramming_flow": [
"15"
],
"filament_type": [
"PETG"
],

View File

@@ -38,9 +38,6 @@
"filament_max_volumetric_speed": [
"6"
],
"filament_multitool_ramming_flow": [
"6"
],
"filament_type": [
"PHA"
],

View File

@@ -38,9 +38,6 @@
"filament_max_volumetric_speed": [
"12"
],
"filament_multitool_ramming_flow": [
"20"
],
"filament_scarf_seam_type": [
"none"
],

View File

@@ -9,9 +9,6 @@
"filament_flow_ratio": [
"0.98"
],
"filament_multitool_ramming_flow": [
"10"
],
"slow_down_layer_time": [
"8"
]

View File

@@ -38,9 +38,6 @@
"filament_max_volumetric_speed": [
"12"
],
"filament_multitool_ramming_flow": [
"12"
],
"filament_type": [
"PP"
],

View File

@@ -47,9 +47,6 @@
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"8"
],
"filament_type": [
"PPA-CF"
],

View File

@@ -43,9 +43,6 @@
"filament_max_volumetric_speed": [
"4"
],
"filament_multitool_ramming_flow": [
"4"
],
"filament_type": [
"PPS"
],

View File

@@ -40,15 +40,6 @@
"filament_max_volumetric_speed": [
"12"
],
"filament_multitool_ramming": [
"0"
],
"filament_multitool_ramming_flow": [
"6"
],
"filament_multitool_ramming_volume": [
"0.1"
],
"filament_soluble": [
"1"
],

View File

@@ -11,9 +11,6 @@
"filament_max_volumetric_speed": [
"12"
],
"filament_multitool_ramming_flow": [
"12"
],
"filament_type": [
"SBS"
],

View File

@@ -43,15 +43,6 @@
"filament_max_volumetric_speed": [
"3.2"
],
"filament_multitool_ramming": [
"0"
],
"filament_multitool_ramming_flow": [
"3.2"
],
"filament_multitool_ramming_volume": [
"0.1"
],
"filament_retraction_length": [
"0.4"
],

View File

@@ -17,9 +17,6 @@
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"8"
],
"filament_vendor": [
"eSUN"
],

View File

@@ -17,9 +17,6 @@
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"8"
],
"filament_vendor": [
"eSUN"
],

View File

@@ -21,9 +21,6 @@
"filament_max_volumetric_speed": [
"6"
],
"filament_multitool_ramming_flow": [
"6"
],
"filament_vendor": [
"eSUN"
],

View File

@@ -1,6 +1,6 @@
{
"name": "Snapmaker",
"version": "02.04.00.08",
"version": "02.04.00.07",
"force_update": "0",
"description": "Snapmaker configurations",
"machine_model_list": [
@@ -426,6 +426,10 @@
"name": "0.16 Optimal @Snapmaker U1 (0.4 nozzle)",
"sub_path": "process/0.16 Optimal @Snapmaker U1 (0.4 nozzle).json"
},
{
"name": "0.20 Bambu Support W @Snapmaker U1 (0.4 nozzle)",
"sub_path": "process/0.20 Bambu Support W @Snapmaker U1 (0.4 nozzle).json"
},
{
"name": "0.20 Quality @Snapmaker U1 (0.4 nozzle)",
"sub_path": "process/0.20 Quality @Snapmaker U1 (0.4 nozzle).json"
@@ -482,66 +486,6 @@
"name": "fdm_process_U1_0.8_common",
"sub_path": "process/fdm_process_U1_0.8_common.json"
},
{
"name": "fdm_process_U1_0.06_nozzle_0.2",
"sub_path": "process/fdm_process_U1_0.06_nozzle_0.2.json"
},
{
"name": "fdm_process_U1_0.08_nozzle_0.2",
"sub_path": "process/fdm_process_U1_0.08_nozzle_0.2.json"
},
{
"name": "fdm_process_U1_0.10_nozzle_0.2",
"sub_path": "process/fdm_process_U1_0.10_nozzle_0.2.json"
},
{
"name": "fdm_process_U1_0.12_nozzle_0.2",
"sub_path": "process/fdm_process_U1_0.12_nozzle_0.2.json"
},
{
"name": "fdm_process_U1_0.14_nozzle_0.2",
"sub_path": "process/fdm_process_U1_0.14_nozzle_0.2.json"
},
{
"name": "fdm_process_U1_0.18_nozzle_0.6",
"sub_path": "process/fdm_process_U1_0.18_nozzle_0.6.json"
},
{
"name": "fdm_process_U1_0.24_nozzle_0.6",
"sub_path": "process/fdm_process_U1_0.24_nozzle_0.6.json"
},
{
"name": "fdm_process_U1_0.24_nozzle_0.8",
"sub_path": "process/fdm_process_U1_0.24_nozzle_0.8.json"
},
{
"name": "fdm_process_U1_0.30_nozzle_0.6",
"sub_path": "process/fdm_process_U1_0.30_nozzle_0.6.json"
},
{
"name": "fdm_process_U1_0.32_nozzle_0.8",
"sub_path": "process/fdm_process_U1_0.32_nozzle_0.8.json"
},
{
"name": "fdm_process_U1_0.36_nozzle_0.6",
"sub_path": "process/fdm_process_U1_0.36_nozzle_0.6.json"
},
{
"name": "fdm_process_U1_0.40_nozzle_0.8",
"sub_path": "process/fdm_process_U1_0.40_nozzle_0.8.json"
},
{
"name": "fdm_process_U1_0.42_nozzle_0.6",
"sub_path": "process/fdm_process_U1_0.42_nozzle_0.6.json"
},
{
"name": "fdm_process_U1_0.48_nozzle_0.8",
"sub_path": "process/fdm_process_U1_0.48_nozzle_0.8.json"
},
{
"name": "fdm_process_U1_0.56_nozzle_0.8",
"sub_path": "process/fdm_process_U1_0.56_nozzle_0.8.json"
},
{
"name": "0.06 High Quality @Snapmaker U1 (0.2 nozzle)",
"sub_path": "process/0.06 High Quality @Snapmaker U1 (0.2 nozzle).json"
@@ -1564,6 +1508,10 @@
"name": "Snapmaker PLA Metal @U1",
"sub_path": "filament/Snapmaker PLA Metal @U1.json"
},
{
"name": "Snapmaker PLA Silk @U1",
"sub_path": "filament/Snapmaker PLA Silk @U1.json"
},
{
"name": "Snapmaker PLA Silk",
"sub_path": "filament/Snapmaker PLA Silk.json"
@@ -1723,214 +1671,6 @@
{
"name": "Snapmaker PLA Matte @U1 base",
"sub_path": "filament/Snapmaker PLA Matte @U1 base.json"
},
{
"name": "Polymaker PLA @U1 base",
"sub_path": "filament/Polymaker PLA @U1 base.json"
},
{
"name": "Polymaker Silk PLA Family @U1",
"sub_path": "filament/Polymaker Silk PLA Family @U1.json"
},
{
"name": "Polymaker Tough PLA Family @U1",
"sub_path": "filament/Polymaker Tough PLA Family @U1.json"
},
{
"name": "Snapmaker Breakaway Support For PLA @U1 0.2 nozzle",
"sub_path": "filament/Snapmaker Breakaway Support For PLA @U1 0.2 nozzle.json"
},
{
"name": "Snapmaker Breakaway Support For PLA @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker Breakaway Support For PLA @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker Breakaway Support For PLA @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker Breakaway Support For PLA @U1 0.8 nozzle.json"
},
{
"name": "Snapmaker PETG HF @U1 base2",
"sub_path": "filament/Snapmaker PETG HF @U1 base2.json"
},
{
"name": "Snapmaker PETG Translucent @U1 base",
"sub_path": "filament/Snapmaker PETG Translucent @U1 base.json"
},
{
"name": "Snapmaker PLA Basic @U1 base",
"sub_path": "filament/Snapmaker PLA Basic @U1 base.json"
},
{
"name": "Snapmaker PLA Full Spectrum @U1 0.4 nozzle",
"sub_path": "filament/Snapmaker PLA Full Spectrum @U1 0.4 nozzle.json"
},
{
"name": "Snapmaker PLA Glow @U1 base",
"sub_path": "filament/Snapmaker PLA Glow @U1 base.json"
},
{
"name": "Snapmaker PLA Matte @U1 base2",
"sub_path": "filament/Snapmaker PLA Matte @U1 base2.json"
},
{
"name": "Snapmaker PLA Silk @U1 0.2 nozzle",
"sub_path": "filament/Snapmaker PLA Silk @U1 0.2 nozzle.json"
},
{
"name": "Snapmaker PLA Silk @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker PLA Silk @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker PLA Silk @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker PLA Silk @U1 0.8 nozzle.json"
},
{
"name": "Snapmaker PLA SnapSpeed @U1 base2",
"sub_path": "filament/Snapmaker PLA SnapSpeed @U1 base2.json"
},
{
"name": "Snapmaker PLA Translucent @U1 base",
"sub_path": "filament/Snapmaker PLA Translucent @U1 base.json"
},
{
"name": "Snapmaker PLA Wood @U1 0.4 nozzle",
"sub_path": "filament/Snapmaker PLA Wood @U1 0.4 nozzle.json"
},
{
"name": "Snapmaker PLA Wood @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker PLA Wood @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker PLA Wood @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker PLA Wood @U1 0.8 nozzle.json"
},
{
"name": "Snapmaker PLA-CF @U1 0.4 nozzle",
"sub_path": "filament/Snapmaker PLA-CF @U1 0.4 nozzle.json"
},
{
"name": "Snapmaker PLA-CF @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker PLA-CF @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker PLA-CF @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker PLA-CF @U1 0.8 nozzle.json"
},
{
"name": "Snapmaker PVA @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker PVA @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker PVA @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker PVA @U1 0.8 nozzle.json"
},
{
"name": "Snapmaker TPU 90A @U1",
"sub_path": "filament/Snapmaker TPU 90A @U1.json"
},
{
"name": "Snapmaker TPU 90A @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker TPU 90A @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker TPU 90A @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker TPU 90A @U1 0.8 nozzle.json"
},
{
"name": "Snapmaker TPU 95A HF @U1",
"sub_path": "filament/Snapmaker TPU 95A HF @U1.json"
},
{
"name": "Snapmaker TPU 95A HF @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker TPU 95A HF @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker TPU 95A HF @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker TPU 95A HF @U1 0.8 nozzle.json"
},
{
"name": "Snapmaker PLA Silk @U1",
"sub_path": "filament/Snapmaker PLA Silk @U1.json"
},
{
"name": "Polymaker General PLA Family @U1",
"sub_path": "filament/Polymaker General PLA Family @U1.json"
},
{
"name": "Snapmaker PETG HF @U1 0.2 nozzle",
"sub_path": "filament/Snapmaker PETG HF @U1 0.2 nozzle.json"
},
{
"name": "Snapmaker PETG HF @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker PETG HF @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker PETG HF @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker PETG HF @U1 0.8 nozzle.json"
},
{
"name": "Snapmaker PETG Translucent @U1 0.2 nozzle",
"sub_path": "filament/Snapmaker PETG Translucent @U1 0.2 nozzle.json"
},
{
"name": "Snapmaker PETG Translucent @U1 0.4 nozzle",
"sub_path": "filament/Snapmaker PETG Translucent @U1 0.4 nozzle.json"
},
{
"name": "Snapmaker PETG Translucent @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker PETG Translucent @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker PETG Translucent @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker PETG Translucent @U1 0.8 nozzle.json"
},
{
"name": "Snapmaker PLA Basic @U1",
"sub_path": "filament/Snapmaker PLA Basic @U1.json"
},
{
"name": "Snapmaker PLA Glow @U1 0.4 nozzle",
"sub_path": "filament/Snapmaker PLA Glow @U1 0.4 nozzle.json"
},
{
"name": "Snapmaker PLA Matte @U1 0.2 nozzle",
"sub_path": "filament/Snapmaker PLA Matte @U1 0.2 nozzle.json"
},
{
"name": "Snapmaker PLA Matte @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker PLA Matte @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker PLA Matte @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker PLA Matte @U1 0.8 nozzle.json"
},
{
"name": "Snapmaker PLA SnapSpeed @U1 0.2 nozzle",
"sub_path": "filament/Snapmaker PLA SnapSpeed @U1 0.2 nozzle.json"
},
{
"name": "Snapmaker PLA SnapSpeed @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker PLA SnapSpeed @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker PLA SnapSpeed @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker PLA SnapSpeed @U1 0.8 nozzle.json"
},
{
"name": "Snapmaker PLA Translucent @U1 0.2 nozzle",
"sub_path": "filament/Snapmaker PLA Translucent @U1 0.2 nozzle.json"
},
{
"name": "Snapmaker PLA Translucent @U1 0.4 nozzle",
"sub_path": "filament/Snapmaker PLA Translucent @U1 0.4 nozzle.json"
},
{
"name": "Snapmaker PLA Translucent @U1 0.6 nozzle",
"sub_path": "filament/Snapmaker PLA Translucent @U1 0.6 nozzle.json"
},
{
"name": "Snapmaker PLA Translucent @U1 0.8 nozzle",
"sub_path": "filament/Snapmaker PLA Translucent @U1 0.8 nozzle.json"
}
],
"machine_list": [

View File

@@ -1,32 +0,0 @@
{
"type": "filament",
"name": "Polymaker General PLA Family @U1",
"inherits": "Polymaker PLA @U1 base",
"from": "system",
"setting_id": "thXfSaUkOAKJ50ey",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"enable_pressure_advance": [
"0"
],
"filament_max_volumetric_speed": [
"15"
],
"filament_vendor": [
"Polymaker"
],
"nozzle_temperature_range_high": [
"230"
],
"temperature_vitrification": [
"62"
],
"textured_plate_temp": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
]
}

View File

@@ -1,107 +0,0 @@
{
"type": "filament",
"name": "Polymaker PLA @U1 base",
"inherits": "fdm_filament_pla",
"from": "system",
"filament_id": "OGFL99",
"instantiation": "false",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"activate_air_filtration": [
"0"
],
"chamber_temperatures": [
"0"
],
"complete_print_exhaust_fan_speed": [
"70"
],
"cool_plate_temp": [
"35"
],
"cool_plate_temp_initial_layer": [
"35"
],
"during_print_exhaust_fan_speed": [
"70"
],
"filament_cost": [
"20"
],
"filament_long_retractions_when_cut": [
"nil"
],
"filament_max_volumetric_speed": [
"12"
],
"filament_minimal_purge_on_wipe_tower": [
"15"
],
"filament_retraction_distances_when_cut": [
"nil"
],
"filament_retraction_length": [
"nil"
],
"filament_retraction_speed": [
"nil"
],
"filament_vendor": [
"Generic"
],
"filament_scarf_seam_type": [
"none"
],
"filament_scarf_height": [
"10%"
],
"filament_scarf_gap": [
"15%"
],
"filament_scarf_length": [
"10"
],
"filament_shrink": [
"100%"
],
"hot_plate_temp": [
"55"
],
"hot_plate_temp_initial_layer": [
"55"
],
"nozzle_temperature": [
"220"
],
"overhang_fan_threshold": [
"50%"
],
"supertack_plate_temp": [
"45"
],
"supertack_plate_temp_initial_layer": [
"45"
],
"slow_down_layer_time": [
"4"
],
"slow_down_min_speed": [
"10"
],
"temperature_vitrification": [
"45"
],
"textured_plate_temp": [
"55"
],
"textured_plate_temp_initial_layer": [
"55"
],
"filament_start_gcode": [
"; Filament gcode\n"
],
"filament_end_gcode": [
"; filament end gcode\n"
]
}

View File

@@ -1,32 +0,0 @@
{
"type": "filament",
"name": "Polymaker Silk PLA Family @U1",
"inherits": "Polymaker PLA @U1 base",
"from": "system",
"setting_id": "h8vIYpXbxFtHPV6e",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_density": [
"1.34"
],
"filament_vendor": [
"Polymaker"
],
"nozzle_temperature": [
"230"
],
"nozzle_temperature_initial_layer": [
"230"
],
"nozzle_temperature_range_high": [
"230"
],
"textured_plate_temp": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
]
}

View File

@@ -1,32 +0,0 @@
{
"type": "filament",
"name": "Polymaker Tough PLA Family @U1",
"inherits": "Polymaker PLA @U1 base",
"from": "system",
"setting_id": "d6CC81Es2hp46bto",
"instantiation": "true",
"compatible_printers": [
"Snapmaker U1 (0.4 nozzle)"
],
"filament_density": [
"1.23"
],
"filament_flow_ratio": [
"0.96"
],
"filament_vendor": [
"Polymaker"
],
"slow_down_layer_time": [
"6"
],
"temperature_vitrification": [
"55"
],
"textured_plate_temp": [
"65"
],
"textured_plate_temp_initial_layer": [
"65"
]
}

View File

@@ -11,6 +11,7 @@
"filament_minimal_purge_on_wipe_tower": [
"15"
],
"pressure_advance": [
"0.05"
]

View File

@@ -17,6 +17,7 @@
"filament_z_hop": [
"0.0"
],
"enable_pressure_advance": [
"1"
],

View File

@@ -17,6 +17,7 @@
"filament_z_hop": [
"0.0"
],
"enable_pressure_advance": [
"1"
],

View File

@@ -17,6 +17,7 @@
"supertack_plate_temp_initial_layer": [
"70"
],
"pressure_advance": [
"0.04"
]

View File

@@ -1,10 +1,10 @@
{
"type": "filament",
"name": "PolyLite Dual PLA @0.2 nozzle",
"inherits": "PolyLite PLA @0.2 nozzle",
"from": "system",
"setting_id": "jhmy4YHi9MuL0DWu",
"instantiation": "true",
"name": "PolyLite Dual PLA @0.2 nozzle",
"setting_id": "jhmy4YHi9MuL0DWu",
"inherits": "PolyLite PLA @0.2 nozzle",
"compatible_printers": [
"Snapmaker A250 Dual (0.2 nozzle)",
"Snapmaker A250 Dual BKit (0.2 nozzle)",

View File

@@ -1,10 +1,10 @@
{
"type": "filament",
"name": "PolyLite J1 PLA @0.2 nozzle",
"inherits": "PolyLite PLA @0.2 nozzle",
"from": "system",
"setting_id": "jFlRSxx0KvN3BOUu",
"instantiation": "true",
"name": "PolyLite J1 PLA @0.2 nozzle",
"setting_id": "jFlRSxx0KvN3BOUu",
"inherits": "PolyLite PLA @0.2 nozzle",
"compatible_printers": [
"Snapmaker J1 (0.2 nozzle)"
]

View File

@@ -1,10 +1,10 @@
{
"type": "filament",
"name": "PolyLite J1 PLA",
"inherits": "PolyLite PLA @base",
"from": "system",
"setting_id": "wcpOTTMyZNPFKyXr",
"instantiation": "true",
"name": "PolyLite J1 PLA",
"setting_id": "wcpOTTMyZNPFKyXr",
"inherits": "PolyLite PLA @base",
"compatible_printers": [
"Snapmaker J1 (0.4 nozzle)",
"Snapmaker J1 (0.6 nozzle)",

View File

@@ -1,9 +1,4 @@
{
"type": "filament",
"name": "PolyLite PETG @Base",
"inherits": "fdm_filament_petg",
"from": "system",
"instantiation": "false",
"filament_density": [
"1.25"
],
@@ -23,6 +18,11 @@
"0"
],
"description": "",
"inherits": "fdm_filament_petg",
"name": "PolyLite PETG @Base",
"type": "filament",
"instantiation": "false",
"from": "system",
"filament_vendor": [
"Polymaker"
]

View File

@@ -17,6 +17,7 @@
"supertack_plate_temp_initial_layer": [
"70"
],
"pressure_advance": [
"0.05"
]

View File

@@ -17,6 +17,7 @@
"supertack_plate_temp_initial_layer": [
"70"
],
"pressure_advance": [
"0.05"
]

View File

@@ -1,10 +1,10 @@
{
"type": "filament",
"name": "PolyLite PLA @0.2 nozzle",
"inherits": "PolyLite PLA @base",
"from": "system",
"setting_id": "s9mdMaFca8SHfji9",
"instantiation": "true",
"name": "PolyLite PLA @0.2 nozzle",
"setting_id": "s9mdMaFca8SHfji9",
"inherits": "PolyLite PLA @base",
"compatible_printers": [
"Snapmaker A250 (0.2 nozzle)",
"Snapmaker A250 BKit (0.2 nozzle)",

View File

@@ -1,10 +1,10 @@
{
"type": "filament",
"name": "PolyLite PLA @base",
"inherits": "fdm_filament_pla",
"from": "system",
"filament_id": "1393866034",
"instantiation": "false",
"name": "PolyLite PLA @base",
"filament_id": "1393866034",
"inherits": "fdm_filament_pla",
"filament_flow_ratio": [
"0.95"
],

View File

@@ -1,10 +1,10 @@
{
"type": "filament",
"name": "PolyTerra Dual PLA @0.2 nozzle",
"inherits": "PolyTerra PLA @0.2 nozzle",
"from": "system",
"setting_id": "UQYgjH1uVxf3d2Nv",
"instantiation": "true",
"name": "PolyTerra Dual PLA @0.2 nozzle",
"setting_id": "UQYgjH1uVxf3d2Nv",
"inherits": "PolyTerra PLA @0.2 nozzle",
"compatible_printers": [
"Snapmaker A250 Dual (0.2 nozzle)",
"Snapmaker A250 Dual BKit (0.2 nozzle)",

View File

@@ -1,10 +1,10 @@
{
"type": "filament",
"name": "PolyTerra J1 PLA @0.2 nozzle",
"inherits": "PolyTerra PLA @0.2 nozzle",
"from": "system",
"setting_id": "mxKHxGmBZzPLS82O",
"instantiation": "true",
"name": "PolyTerra J1 PLA @0.2 nozzle",
"setting_id": "mxKHxGmBZzPLS82O",
"inherits": "PolyTerra PLA @0.2 nozzle",
"compatible_printers": [
"Snapmaker J1 (0.2 nozzle)"
]

View File

@@ -1,10 +1,10 @@
{
"type": "filament",
"name": "PolyTerra J1 PLA",
"inherits": "PolyTerra PLA @base",
"from": "system",
"setting_id": "4MZWZX7QPBzxdEGj",
"instantiation": "true",
"name": "PolyTerra J1 PLA",
"setting_id": "4MZWZX7QPBzxdEGj",
"inherits": "PolyTerra PLA @base",
"compatible_printers": [
"Snapmaker J1 (0.4 nozzle)",
"Snapmaker J1 (0.6 nozzle)",

View File

@@ -1,10 +1,10 @@
{
"type": "filament",
"name": "PolyTerra PLA @0.2 nozzle",
"inherits": "PolyTerra PLA @base",
"from": "system",
"setting_id": "6W1ygT08NordBdIW",
"instantiation": "true",
"name": "PolyTerra PLA @0.2 nozzle",
"setting_id": "6W1ygT08NordBdIW",
"inherits": "PolyTerra PLA @base",
"compatible_printers": [
"Snapmaker A250 (0.2 nozzle)",
"Snapmaker A250 BKit (0.2 nozzle)",

Some files were not shown because too many files have changed in this diff Show More