Compare commits

..

60 Commits

Author SHA1 Message Date
SoftFever
8c050894db Ship one preset cache per vendor in place of the profile JSONs
Each vendor's system presets serialize into a single <vendor>.opc built at
package time, and a shipped build carries that file alone — the profile JSON
and its sub-file tree are pruned. The vendor loader, the setup wizard's profile
list and the resource installer all read a vendor through its cache, falling
back to parsing whenever one is absent, stale or unreadable, so the cache stays
an optimization and never a source of truth. Caches hold presets in source form
and resolve inheritance at load, through the same code the JSON path uses.
2026-08-05 21:16:41 +08:00
ExPikaPaka
55fb4703c9 Remove not used files 2026-08-05 21:15:12 +08:00
ExPikaPaka
b25d998fc5 Ignore cache for future 2026-08-05 21:15:11 +08:00
ExPikaPaka
bc6f3aa7f7 Transfer all Preset fields from cache via move assignmet
apply_vendor_preset_group was copying fields manually and missed
bundle_id, user_id, base_id, sync_info, updated_time, key_values,
ini_str. Replace field-by-field copy with move assignment of the
fully-deserialized Preset, then restore the vendor pointer which
is excluded from serialization.
2026-08-05 21:15:11 +08:00
ExPikaPaka
5bf0e09781 Rvert from per-verndor to single cache file
Replace N per-vendor .cache files with a single system_presets.cache
that holds all vendors and presets in one serialized blob.

Cache load is now all-or-nothing: on hit all vendors are applied from
the bundle (sub-second); on miss all vendors are parsed from JSON and
a fresh bundle is written to the user cache dir.

Invalidation is driven by bundle_key - a sorted concatenation of all
vendor JSON version strings. Any vendor update invalidates the whole
cache and triggers re-parse on next launch.

Guide wizard (WebGuideDialog) loads the bundled cache into a plain
PresetBundle instead of a separate VendorGuideData struct, removing
the duplicate data model.

generate_system_cache simplified from a per-vendor loop to a single
save_system_presets_cache() call producing one output file.
2026-08-05 21:15:11 +08:00
ExPikaPaka
3fea10a711 Merge branch 'main' into feature/cache_profiles_and_optimize_loading_speed 2026-08-05 21:15:11 +08:00
ExPikaPaka
0df7d9e661 Add a bit more tests 2026-08-05 21:15:11 +08:00
ExPikaPaka
603b1eaa87 Add tests for Cache system 2026-08-05 21:15:10 +08:00
ExPikaPaka
cae9c6dfa6 Minimize field duplication by moving Cache thing into PresetBundle 2026-08-05 21:14:56 +08:00
ExPikaPaka
1f35b28a50 Serealize all value fields for Preset class to minimize regression later 2026-08-05 21:14:56 +08:00
ExPikaPaka
68e7810c73 Update check for stale cache 2026-08-05 21:14:56 +08:00
ExPikaPaka
2ac64f7171 Revert json cache back 2026-08-05 21:14:56 +08:00
ExPikaPaka
c0cfcbbc64 Fix build for windows arm64 2026-08-05 21:14:55 +08:00
ExPikaPaka
3c6d104c71 Remove leftover cache file 2026-08-05 21:14:55 +08:00
ExPikaPaka
d85b555748 Skip invalid vendors 2026-08-05 21:14:55 +08:00
ExPikaPaka
ff92efa91f Remove BOM added by VSC 2026-08-05 21:14:55 +08:00
ExPikaPaka
89b66daeca Use get_vendor_cache_key() to match cache keys written by the app 2026-08-05 21:14:55 +08:00
ExPikaPaka
1f29ef3adc Fix use-after-free in CallAfter lambda; replace raw thread pointer with unique_ptr 2026-08-05 21:14:55 +08:00
ExPikaPaka
fe216a4ded Remove CachedPrinterModel/VendorProfile/Preset mirror structs from VendorCache 2026-08-05 21:14:55 +08:00
ExPikaPaka
8ea0b121e7 Add cereal serialize() to VendorProfile, PrinterModel, Preset, and Semver 2026-08-05 21:14:55 +08:00
ExPikaPaka
1d19e39625 Simplify code a bit more 2026-08-05 21:14:55 +08:00
ExPikaPaka
273d325066 Simplify code by mergin it in PresetBundle 2026-08-05 21:14:55 +08:00
ExPikaPaka
5ad7a3eb5d Generate cache per vendor 2026-08-05 21:14:55 +08:00
ExPikaPaka
c12e348ce3 Add inspecting tool and fix CI cache generation 2026-08-05 21:14:55 +08:00
ExPikaPaka
b34923fc7c Add cache to GuideDialog as previos version didn't work as expected 2026-08-05 21:14:55 +08:00
ExPikaPaka
a1eba4b2c8 Handle corrupted files 2026-08-05 21:14:55 +08:00
ExPikaPaka
6b45fa2c97 Add partial cache generation when only one of the vendros is changed to speed up recalculation time 2026-08-05 21:14:55 +08:00
ExPikaPaka
6b3e4f18e1 Add CI\CD step to prepare cache file in ahead of time so user does not need to wait 2026-08-05 21:14:55 +08:00
ExPikaPaka
25b1577c45 Integrate caching into WebGuideDialog which speeds up time of SetupWizzard and PrinterSelection dialog 2026-08-05 21:13:48 +08:00
ExPikaPaka
69c1f6880c Removing user\bundle serialization and keeping it only for system presets 2026-08-05 21:13:48 +08:00
ExPikaPaka
86bedc1e37 Add caching system for presets 2026-08-05 21:13:48 +08:00
SoftFever
a10d9e77cf Make the wipe tower's planner flush and dwell work on Klipper (#15133)
# Description

On Klipper the wipe tower's motion-queue synchronization silently did
nothing. Klipper acts on commands the moment it parses them, and its
`G4` reads only `P` in milliseconds — it ignores `S` — so the `G4 S0`
the tower used to flush the queue before a temperature change never
synchronized anything, and the cooling delay after a filament's cooling
moves passed instantly instead of waiting. The tower now emits `M400`
for the flush and `G4 P<ms>` for the dwell when the flavor is Klipper.

Only `gcode_flavor = klipper` is affected; G-code for every other flavor
is byte-identical, so no shipped profile or existing project file
changes.

# Screenshots/Recordings/Graphs

<!--
> Please attach relevant screenshots to showcase the UI changes.
> Please attach images that can help explain the changes.
-->

## Tests

<!--
> Please describe the tests that you have conducted to verify the
changes made in this PR.
-->

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-08-05 18:21:05 +08:00
SoftFever
1d023216f2 clean up 2026-08-05 18:09:36 +08:00
SoftFever
dc2796209f feat: printer agent UI (#15111)
# Description

Changes the device tab to render native wxWidgets UI and route the UI
via printer agents.

When the flag introduced in #15110 is enabled, the tab bar will show
both the native wxWidgets device tab and webview tab under the name
device (legacy)

This is a prerequisite for implementing the printer agent workflow.

<img width="3377" height="1378" alt="image"
src="https://github.com/user-attachments/assets/d72b646b-e6c6-4e10-b5d4-fc989debfca8"
/>
<img width="3377" height="1378" alt="image"
src="https://github.com/user-attachments/assets/2356c0f8-bffe-4cd5-bf4d-31f4b5c7706e"
/>

<!--
> A guide for users on how to download the artifacts from this PR.
-->

[How to Download Pull Requests Artifacts for
Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-08-05 17:25:23 +08:00
SoftFever
194ef34080 Wait in the wipe tower with a millisecond dwell on Klipper
The wipe tower's "Delay after unloading" never happened on Klipper. It was
emitted as G4 S<seconds>, and Klipper's G4 reads only the P parameter, in
milliseconds, so the pause was silently skipped. The option now produces a
dwell Klipper actually performs.

Also corrects the planner flush rationale, which cited an extruder position
reset that Klipper resolves at parse time and does not need synchronized, and
adds end-to-end coverage that slices a two-filament print and checks the
emitted wipe tower G-code on both a Klipper and a non-Klipper flavor.

No change to any other firmware flavor's output, and no shipped profile sets a
non-zero delay, so no shipped profile's output moves either.
2026-08-05 17:15:35 +08:00
SoftFever
4e1caa39eb Flush the wipe tower planner queue with M400 on Klipper
The wipe tower emitted G4 S0 to make the firmware finish its queued moves
before commands that must not take effect early. Klipper's G4 reads only the
P parameter, so that flush never happened there and a temperature change could
land seconds ahead of the moves it was meant to follow. Klipper now gets M400
instead, through one helper shared by both wipe tower implementations.

No change to any other firmware flavor's output, so no shipped profile or saved
project is affected.
2026-08-05 17:15:35 +08:00
Ian Chua
38f5c84e7f fix: regression error 2026-08-05 16:20:58 +08:00
Ian Chua
4d8ce0e8a7 Merge branch 'main' into feat/printer-agent-ui 2026-08-05 13:40:20 +08:00
Ian Chua
38cb1ae8d1 Add developer flag for printer agents (#15110) 2026-08-05 13:40:05 +08:00
Ian Chua
32f82b64e7 fix: enable both device tabs 2026-08-05 13:07:32 +08:00
Ian Chua
e56d7aeb80 Merge branch 'feat/printer-agent-ui' of https://github.com/OrcaSlicer/OrcaSlicer into feat/printer-agent-ui 2026-08-05 13:07:08 +08:00
Ian Chua
4758dc6c68 Merge branch 'main' into feat/printer-agent-ui 2026-08-05 13:06:47 +08:00
Ian Chua
596cbb8b2d Keep printer-agent error codes available to UI workflow 2026-08-05 11:39:37 +08:00
SoftFever
6312caaf13 Add filament_retract_length_toolchange/filament_retract_restart_extra_toolchange config and update tool changer printer's profiles (#15039)
* update snapmaker profiles. largely ported for Snapmaker Orca fork

* update prime volume

* set precise_outer_wall to 1

* Update per-material multi-tool ramming to the filament library

* Add per-filament overrides for toolchange retraction

* Set toolchange retraction per filament for Snapmaker U1

* set default support type to tree

* format snapmaker profiles
2026-08-05 00:13:27 +08:00
SoftFever
0051768206 Smooth out the spiral lift when arc fitting is disabled (#15118)
The linear approximation used a heuristic segment count clamped to 4..16, so the
lift ran as a coarse polygon. Every vertex is a direction change large enough to
hit the firmware's jerk limit, forcing a decelerate/accelerate at each corner —
the lift micro-stutters instead of running at speed. The segment count now comes
from the chord deviation against the slicing resolution, reusing
Geometry::ArcWelder::arc_discretization_steps, which keeps the turn at each
vertex shallow enough for the firmware to carry speed through the whole move.

Points are emitted through GCodeG1Formatter so they carry the same quantization
as the rest of the G-code, and the move comment now trails the feedrate line to
match _travel_to_z and the G2/G3 branch. No change when arc fitting is enabled.
2026-08-05 00:09:46 +08:00
Ian Bassi
1d078e005a Mouse ear Wiki redirect (#15115)
Based in https://github.com/OrcaSlicer/OrcaSlicer/pull/15015 and https://github.com/OrcaSlicer/OrcaSlicer_WIKI/pull/323
2026-08-04 11:37:05 -03:00
Kris Austin
82759d3899 fix: make the error dialog caret point at the character it's blaming (#14886)
* fix: make the error dialog caret point at the character it's blaming

Custom G-code parse errors print the offending line with a '^' under the
character that broke, positioned with spaces so it only lines up in a
fixed-width font. Since v2.3.2 these dialogs rendered entirely in the
proportional UI font, so the caret drifted left of its column and landed
on unrelated text.

Render only the code excerpts (the offending source line and its caret) in
the fixed-width face, leaving the surrounding prose in the UI font, and
reserve the horizontal scrollbar's height so a long line does not clip.
Rename the flag to has_code_excerpts to match what it now means.

Fixes #14869

* refactor(GUI): use <code> instead of <tt> for error excerpts

wxHTML maps <tt>, <code>, <kbd> and <samp> to the same fixed-width
handler, so this renders identically. <code> is the non-deprecated
tag and matches what the original code used.

* fix(GUI): align the error caret with real spaces, not &nbsp;

The caret line was padded with &nbsp; so its spaces would survive inline
HTML. wxHTML measures every glyph by its font extent, so where the fixed
font lacks a U+00A0 glyph the fallback renders it about twice as wide, and
the all-&nbsp; caret line outran the source, drifting the ^ to the right.

Wrap the excerpts in a small <excerpt> tag, registered on the dialog's own
parser, that switches on wxHTML literal-whitespace mode so the caret uses
real spaces that match the source column in any font. It sits inside <code>
for the fixed face; <pre> would do both but forces a blank line above it.

---------

Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
2026-08-04 22:01:45 +08:00
Ian Bassi
59155f26ac Build Arch Fix (#15107)
Arch Fix
2026-08-04 21:56:15 +08:00
Ian Chua
79dcace1ac Add unsupported-command feedback to the device UI 2026-08-04 21:26:50 +08:00
Andrew
8dfc7a14b9 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:11 +08:00
Andrew
1534268183 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:11 +08:00
Andrew
501af81ba9 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:10 +08:00
Ian Chua
16c44940d2 Add developer flag for printer agents 2026-08-04 18:12:10 +08:00
yw4z
40eab797c6 match em_unit value for on_dpi_change for linux (#15043)
* Update GUI_Utils.hpp

* Update GUI_Utils.hpp
2026-08-04 08:45:31 +08:00
Kiss Lorand
ca7fbfb007 Fix missing overhang wall when no partial counterbore bridge is generated (#15100) 2026-08-03 18:02:48 -03:00
Mikhail f. Shiryaev
7b404596e9 Add Skip G-code config block to exclude the config comments from G-code files (#12455)
Add feature to skip CONFIG_BLOCK in G-code files
2026-08-03 15:10:01 -03:00
Kris Austin
06ef58bad8 test: replace the disabled convex_hull_2d test (#14892)
test(libslic3r): replace the disabled convex_hull_2d test, closing #11269

The last "failing libslic3r test" from #11269 was the disabled
SCENARIO("2D convex hull of sinking object", "[3mf][.]") in test_3mf.cpp.
It checked ModelObject::convex_hull_2d for a sinking object against
PrusaSlicer's reference hull, but Orca's convex_hull_2d does not clip
geometry below the bed the way PrusaSlicer's its_convex_hull_2d_above does,
so the reference never matched. The test also wrote a debug mesh to a
hardcoded /tmp path and its comparison loop was inverted.

Remove it and add tests/libslic3r/test_model.cpp characterizing
convex_hull_2d on non-sinking transforms (identity and scale+offset),
where the projected footprint is unambiguous. Homed in a Model test file
since it exercises ModelObject, not 3MF.
2026-08-03 22:29:00 +08:00
SoftFever
74c4a7e450 Support printer specific filament profiles in the OrcaFilamentLibrary (#15101)
* Support printer specific filament profiles in the Orca Filament Library
2026-08-03 22:25:50 +08:00
Noisyfox
dbb991bf07 Fix gizmo being closed after releasing mouse outside the gizmo floating window (#15095)
* Fix gizmo being closed after releasing mouse outside the gizmo floating window

The left up event of a drag started on the gizmo floating window (e.g.
selecting text in an input field) and released over the bed was treated
as a click on the plate, which deselected the objects and closed the
active gizmo. Add the ignore_left_up guard to the plate select branch,
matching the deselect branch above.

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix Emboss gizmo being closed after releasing mouse outside its floating window

The Emboss gizmo has its own close-on-click-away handler
(on_mouse_change_selection) that was not protected against left up events
originating from ImGui windows, so the gizmo was still closed when a drag
started on its floating window (e.g. selecting text in the input field)
ended over the 3D scene. Expose the canvas's ignore_left_up state to
gizmos and skip the close check for such releases.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-03 18:34:15 +08:00
Noisyfox
66d3f3f9c3 imgui: Clamp mouse y-coordinate in multi-line click/drag to text bounds (#15052)
* imgui: Clamp mouse y-coordinate in multi-line click/drag to text bounds

In single-line mode, click and drag already clamped y to the line's
y-coordinate so the cursor would continue to follow the x-position when
the mouse went off the top or bottom of the text.  Multi-line mode did
not clamp, so stb_text_locate_coord() would return 0 (above) or n
(below), snapping the cursor to the very start or end of text and
ignoring the x-coordinate entirely.

Now both modes walk the row layout to compute the top of the first row
(y_min) and bottom of the last row (y_max, minus half a line height to
add tolerance for rounding), then clamp y to that range before passing
it to stb_text_locate_coord().  This means dragging or clicking above
the text now places the cursor on the first line at the x-coordinate,
and dragging/clicking below places it on the last line at the
x-coordinate, matching the single-line precedent.

* Fix issue that cursor cannot be placed at the last empty line
2026-08-03 18:34:05 +08:00
318 changed files with 14641 additions and 8716 deletions

5
.gitattributes vendored
View File

@@ -1,2 +1,7 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Shell scripts are run by Git Bash on Windows CI, which cannot read a script
# with CRLF line endings: it fails on the first line. Windows checkouts default
# to core.autocrlf=true, so keep these LF whatever the platform.
*.sh text eol=lf

View File

@@ -162,6 +162,14 @@ jobs:
retention-days: 5
if-no-files-found: error
- name: Build system preset cache (macOS)
if: runner.os == 'macOS' && !inputs.macos-combine-only
working-directory: ${{ github.workspace }}
shell: bash
# The bundle was already packed from resources/, so the caches have to be
# installed into it here; the source tree keeps its JSONs for later jobs.
run: ./scripts/build_preset_cache.sh -b build/${{ inputs.arch }} build/${{ inputs.arch }}/OrcaSlicer/OrcaSlicer.app/Contents/Resources/profiles
- name: Pack macOS app bundle ${{ inputs.arch }}
if: runner.os == 'macOS' && !inputs.macos-combine-only
working-directory: ${{ github.workspace }}
@@ -390,6 +398,13 @@ jobs:
if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 tests } else { .\build_release_vs.bat slicer tests }
shell: pwsh
- name: Build system preset cache (Windows)
if: runner.os == 'Windows'
shell: cmd
# Shipped into both the already-installed tree (portable zip, MSIX) and
# the checkout cpack re-installs from when it builds the NSIS installer.
run: scripts\build_preset_cache.bat "%BUILD_DIR%" "resources\profiles" "%BUILD_DIR%\OrcaSlicer\resources\profiles"
- name: Pack unit tests Win
if: runner.os == 'Windows'
working-directory: ${{ github.workspace }}
@@ -539,6 +554,20 @@ jobs:
retention-days: 5
if-no-files-found: error
- name: Build system preset cache (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
# Both were packed from resources/ before the caches existed, so the
# AppImage is unpacked first and the caches shipped into it and into
# the package tree; the source tree keeps its JSONs for later steps.
appimage=$(find build -maxdepth 1 -name "OrcaSlicer_Linux_AppImage*.AppImage" | head -1)
chmod +x "$appimage"
"$appimage" --appimage-extract
./scripts/build_preset_cache.sh -b build build/package/resources/profiles squashfs-root/resources/profiles
appimagetool=$(find build -name "appimagetool.AppImage" | head -1)
ARCH=$(uname -m) "$appimagetool" --appimage-extract-and-run squashfs-root "$appimage"
rm -rf squashfs-root
# Ship the freshly-built validator so slice_check_linux (build_all.yml)
# can slice-sweep the shipped profiles with this PR's engine. Taken from
# the aarch64 leg so the sweep also exercises the arm build; x86_64 on

1
.gitignore vendored
View File

@@ -49,3 +49,4 @@ internal_docs/
# Python bytecode
__pycache__/
*.pyc
*.opc

View File

@@ -567,6 +567,8 @@ if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target OrcaSlicer
echo "Building OrcaSlicer_profile_validator .."
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target OrcaSlicer_profile_validator
echo "Building generate_system_cache ..."
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target generate_system_cache
./scripts/run_gettext.sh
fi
if [[ -n "${BUILD_TESTS}" ]] ; then

View File

@@ -465,6 +465,57 @@ 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;
@@ -485,6 +536,50 @@ 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;

288
docs/HLSD/preset-cache.md Normal file
View File

@@ -0,0 +1,288 @@
# System Preset Cache — High Level Design
## Why it exists
OrcaSlicer ships tens of thousands of system preset JSON files. Every launch used to
parse all of them: read each vendor profile, walk its machine, process and filament
sub-files, resolve inheritance, and build the preset collections from scratch. That
parse dominated startup, and it produced the same result every time, because system
presets only change when the app is updated or a profile update is installed.
The preset cache replaces that parse with a read. Each vendor's presets are serialized
once — at build time, in CI — into a single binary file the app reads in one pass. The
read replaces the file walk and the JSON parsing, which is where the time went;
resolving inheritance and registering the presets still runs at load, through the same
code the JSON path uses, so the result is the parse's result without the parse.
The cache is **only ever an optimization**. Every rule below exists to guarantee that a
cache is either provably equivalent to parsing the JSONs, or rejected. There is no
"mostly right" cache.
## The unit is one vendor
A cache covers exactly one vendor. `BBL.opc` sits beside `BBL.json` and holds
everything `BBL.json` and the `BBL/` sub-file tree would have produced.
Per-vendor granularity is what makes the system practical:
- A vendor whose profile is bumped invalidates only its own cache. The other 60-odd
vendors keep theirs — even when the bumped vendor is the shared Orca filament
library everyone else inherits from.
- The setup wizard, which loads vendors one at a time, gets the same speedup as
startup without a second code path.
- A vendor with no cache, or a broken one, costs only that vendor a parse.
A cache holds *system* presets only. User presets, project settings and modified
presets are never serialized — they have their own storage and their own lifecycle.
## Where the files live
| Location | Contents on a shipped build | Role |
|---|---|---|
| `resources/profiles/` | `<vendor>.opc` alone — the profile and its preset JSONs both pruned | What the app ships with; the fallback everything falls back to |
| `<data_dir>/system/` | `<vendor>.opc` alone, or `<vendor>.json` + `<vendor>/` after an update | What the user has installed |
| `<data_dir>/system/` (dev build) | `<vendor>.json` + `<vendor>/` + `<vendor>.opc` written at runtime | A developer tree caches as it parses |
Two forms of the same vendor therefore exist, and the system's central rule is that
**a vendor's cache is the whole of it**. Where a cache ships or is installed, no profile
and no preset JSONs sit beside it: the cache carries the presets, the vendor profile,
and the version stamp that says which release it came from. A vendor is "installed" if
either form is present, and its installed version is read from whichever form is there.
What stays beside the caches in `resources/profiles/` is everything that is not a
preset: each vendor's directory of printer thumbnails, cover images, bed models and
hotend meshes, which are read from disk by path and were never part of the cache. Files
that are not vendors at all, `blacklist.json` chief among them, are untouched.
The alternative — shipping both and treating the cache as a sidecar — was rejected. It
doubles the installed size, and it creates a class of bug where the two disagree and
the app's behavior depends on which one a given code path happened to read.
## What a cache file is
A fixed-size header followed by one binary stream.
The header carries a magic number, the cache format version, the payload size and a
CRC32 of the payload. It exists so that a truncated download, a half-written file or a
file from an entirely different program is rejected in microseconds, before anything
tries to interpret it.
The payload opens with the stamps that decide whether the cache may be used at all —
format version, schema fingerprint, vendor name, vendor version — and then the
vendor's data: its vendor profile, three lists of preset entries (process, filament,
machine), and the count of errors the original parse hit.
Each entry is one preset **in source form**: what its JSON sub-file states and nothing
that resolving it derives — the preset's own config diff, the name of the preset it
inherits, and the parse metadata (name, sub-path, description, instantiation, setting
and filament ids, renames). Non-instantiated base presets are stored too; the children
that inherit from them cannot resolve without them.
Three deliberate choices in the layout:
- **Stamps come first**, so the question "what version is this vendor installed at?"
can be answered by reading the first kilobyte. The updater asks that question for
every vendor on every launch; reading tens of megabytes to answer it would give back
the startup time the cache saved.
- **Nothing inherited is baked in.** A filament preset that inherits from the shared
library is stored as its own diff plus its parent's name, and the parent is looked up
when the entry is installed, against whatever library is loaded then. A cache
therefore carries no other vendor's values, and no other vendor's update — the
library's included — can make it stale.
- **Nothing derived is stored.** Default presets, flattened configs, aliases and
lookup maps are all reconstructed at load by the same code the JSON path runs, and
state that path never fills (obsolete-preset lists) is not stored either. This keeps
the cache a record of the vendor's data, not a memory image of the program's state.
## When a cache may be used
A cache is accepted only if every gate below passes. Any failure means "parse the
JSONs instead" — never a hard error, never a partial load.
**1. Integrity.** Magic number, plausible size, CRC32 over the payload.
**2. Cache format version.** A single integer bumped by hand whenever the binary layout
changes in a way nothing else would catch: reordering or retyping a hand-written
serialized field, or changing what the cache's own stamps mean.
**3. Schema fingerprint.** A checksum over the app version and the entire print-config
option schema — every option's key, type, wire ordinal and enum values. This is the
gate that makes the cache safe across development: adding a config option, changing its
type, or reordering the enum values of an existing one all change the fingerprint, so
caches from before the change are rejected without anyone having to remember to bump
anything. It also means a cache never crosses app versions.
**4. Vendor identity and version.** The cache names the vendor it holds and the profile
version it was built from. It is accepted only if that version is at least as new as
the profile now on disk. Where no profile sits beside the cache — the shipped,
cache-only form — the comparison is skipped, because nothing on disk can be newer than
a cache that is the installation.
**5. Every entry installs.** Entries are installed as they are read, and an entry that
cannot be — typically one that inherits a parent the currently loaded filament library
no longer provides — rejects the whole cache, never just the entry. A partial vendor is
not a vendor.
There is deliberately no stamp for the shared filament library. A cache stores its
filaments' inheritance by name and resolves it at load, so a library update changes
what a cache load *produces*, never whether the cache is *valid* — the same file yields
the updated result. This matters most on a shipped build, where a vendor is its cache
and nothing else: a profile update that delivered only the library would otherwise have
stranded every other vendor with a cache it invalidated and no JSONs to fall back on.
A vendor profile with no parsable version is never cached and never served from a
cache. There would be no way to tell later whether the cache had gone stale, and a
cache nothing can invalidate is worse than no cache.
## How a vendor is loaded
Vendors load in a fixed order, because filament inheritance crosses exactly one
boundary: any vendor's filament may inherit from the shared Orca filament library,
and nothing else reaches across vendors. The library therefore goes first, alone;
every other vendor follows in parallel, resolving against it; and the results are
merged in a stable order:
```mermaid
flowchart LR
lib["1 · OrcaFilamentLibrary<br/>loaded first, synchronously"] --> par["2 · every other vendor in parallel,<br/>each into its own bundle, filaments<br/>resolving against the loaded library"] --> merge["3 · bundles merged into one,<br/>sequentially, in stable vendor order"]
```
Whether a vendor comes from its cache or from a parse changes nothing in that
order — both produce the same bundle, so cached and parsed vendors mix freely in
one startup. Each vendor load — startup's and the setup wizard's alike — tries,
in order:
1. The cache in the directory it was asked to load from — normally `<data_dir>/system/`.
2. The shipped cache in `resources/profiles/` — judged against the same installed
profile, so it cannot resurrect a version an update has superseded.
3. Parsing the JSONs — from the data directory if the profile is installed there, and
from `resources/profiles/` otherwise, which on a shipped build only has JSONs for a
vendor that has no cache.
The same decision drawn out — "the gates" are the five acceptance checks above:
```mermaid
flowchart TB
start["load vendor V from a directory dir<br/>— normally &lt;data_dir&gt;/system/"]
start --> stamp["installed version = version of dir/V.json<br/>— or ∞ with no profile there,<br/>the cache then being the installation"]
stamp --> g1{"dir/V.opc<br/>passes all five gates?"}
g1 -- "yes" --> hit(["served from the<br/>installed cache"])
g1 -- "no" --> g2{"resources/profiles/V.opc passes them,<br/>against the same installed version?"}
g2 -- "yes" --> hit2(["served from the<br/>shipped cache"])
g2 -- "no" --> src{"dir/V.json<br/>present?"}
src -- "yes" --> pd["parse the JSONs in dir"]
src -- "no" --> pr["parse the JSONs in<br/>resources/profiles/"]
pd --> ver{"profile version<br/>parsable?"}
pr --> ver
ver -- "yes" --> save(["loaded; dir/V.opc written back —<br/>the next load takes the top path"])
ver -- "no" --> raw(["loaded, never cached"])
```
Serving from a cache is not a memory-image restore. The entries are deserialized and
then installed one by one — inheritance resolved against the presets installed before
them and the currently loaded filament library, configs flattened onto the collection
defaults, validated and registered — by the same function the JSON path calls straight
after parsing a sub-file. The two paths share everything below the parse, which is what
makes a cache-loaded bundle indistinguishable from a JSON-loaded one by construction
rather than by test coverage. Installation also rebuilds each preset's file path from
the local data directory, so a shipped cache never carries the generating machine's
paths.
The second tier is what makes app upgrades work. After an upgrade, a cache the previous
version installed fails the fingerprint gate; the new build's own shipped cache answers
instead, and the user never sees a parse. The stale installed file is simply ignored
until the next profile update overwrites it.
If a parse does happen and the vendor's profile carries a version, the app writes the
cache back beside where it looked for the vendor. That is how a developer build warms
itself up on second launch, and how a vendor delivered by a profile update becomes
cached without waiting for the next release.
## How a vendor is installed
Installing copies from `resources/profiles/` into `<data_dir>/system/`. A shipped build
offers only a cache and a source tree only JSONs, but a partially-generated tree can
have both, at different versions, so the installer picks the form that ships at the
**newer version** and installs only that one:
- Cache newer or equal, and readable → copy the `.opc`, and delete any profile and
vendor directory a previous install left behind, so nothing can shadow it.
- Profile newer, or the cache unreadable or absent → copy the profile and the vendor's
preset JSONs exactly as the app did before caches existed, and delete any stale `.opc`.
The result is that only one form of a vendor is ever present, and it is the newest one
the build has. This matters most for the update check, which compares what is installed
against what installing *would* lay down: if those two disagreed about which form
counts, a vendor could reinstall on every launch forever, or silently never update.
Profile updates delivered over the air always arrive as JSONs, and they win — an
updated vendor's real profile lands in the data directory, the shipped cache is older
and gets rejected, and the vendor is parsed and re-cached. An update that touches only
the filament library needs nothing more: every other vendor's cache stays valid and
simply resolves against the new library on its next load.
## How the caches are produced
Cache generation is a build step, not something a user ever runs.
One script per platform does the whole job, and CI calls it once on each. It builds a
small dev-utility that loads a profiles directory exactly as the app would, with cache
writing enabled, dropping a `<vendor>.opc` beside every vendor profile it parses; then
it copies those caches into each packaged application it was pointed at and deletes
every preset JSON they replace — the vendor's own profile included. Only a vendor that
actually has a cache is pruned, so a vendor the generator skipped keeps its JSONs and is
simply parsed at startup.
Because the schema fingerprint includes the app version, caches must be generated by
the same build that ships them. Generation runs after the build, in the same job.
## Behavior when things go wrong
The system is designed so that no cache problem is fatal:
- **Corrupt, truncated or foreign file** — rejected at the header, vendor parsed.
- **Cache from another app version or schema** — rejected at the fingerprint, vendor
parsed or served from the shipped cache.
- **Stale cache** — rejected on the vendor version stamp, vendor parsed and re-cached.
- **Failure part-way through loading** — a deserialization error, or any entry that
fails to install — rejects the whole cache, and the bundle is reset to a clean state
before falling back, so a half-loaded cache can never leak into the parsed result.
- **A vendor that can be neither read nor parsed** — logged, and left out. The setup
wizard drops that vendor from its list and opens with the rest; startup records the
error alongside the vendors that did load. One broken vendor never takes the app down.
The one genuine limit: on a shipped build a vendor is its cache and nothing else, so a
rejected cache has nothing to fall back to for that vendor. This is by design — the
alternative is shipping every preset twice — and it is why the acceptance gates are
conservative and why CI generates the caches with the same build that ships them. The
recovery path is a profile update, which delivers real JSONs.
It also means nothing may quietly assume a `<vendor>.json` exists. Discovery, version
checks and the update decision all read whichever form is present, and a code path that
enumerates only `*.json` will find no vendors at all in a packaged build.
## Maintenance rules
- **Adding or changing a config option** needs nothing. The fingerprint covers it.
- **Changing a hand-written `serialize()`** — `PresetBundle::CachedPreset`,
`VendorProfile` or its nested types — or the cache's own layout or stamps requires
bumping the cache format version by hand. Nothing else is serialized by hand; the
config payload is covered by the fingerprint.
- **Bumping a vendor profile's version** invalidates that vendor's cache and nothing
else — the filament library's included. Other vendors' caches resolve against the
new library the next time they load.
- **Caches are never committed.** They are build artifacts, generated per build,
ignored by git.
## Where this lives in the tree
| Area | Files |
|---|---|
| Cache format, entry serialization, read/write, load and save | `src/libslic3r/PresetBundle.{hpp,cpp}` |
| Vendor profile serialization | `src/libslic3r/Preset.hpp` |
| Vendor discovery, installed/shipped versions, installation | `src/libslic3r/PresetBundle.cpp` |
| Update and reinstall decisions | `src/slic3r/Utils/PresetUpdater.cpp` |
| Setup wizard and printer-selection dialog | `src/slic3r/GUI/ConfigWizard.cpp`, `src/slic3r/GUI/WebGuideDialog.cpp` |
| Generator tool | `src/dev-utils/generate_system_cache.cpp` |
| Build and packaging script | `scripts/build_preset_cache.{sh,bat}` |
| Tests | `tests/libslic3r/test_vendor_cache.cpp` |

View File

@@ -1,72 +0,0 @@
# 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

@@ -1,205 +0,0 @@
# 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

@@ -1,158 +0,0 @@
# 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

@@ -1,96 +0,0 @@
# 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

@@ -1,248 +0,0 @@
# 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

@@ -1,110 +0,0 @@
# 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

@@ -1,161 +0,0 @@
# 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

@@ -1,138 +0,0 @@
# 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

@@ -1,137 +0,0 @@
# 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

@@ -1,280 +0,0 @@
<!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

@@ -1,249 +0,0 @@
# 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.01",
"version": "02.04.00.02",
"force_update": "0",
"description": "My configurations",
"machine_model_list": [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,12 +25,6 @@
"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": "Normal Lift",
"z_hop_types": "Slope Lift",
"silent_mode": "0",
"single_extruder_multi_material": "1",
"change_filament_gcode": "",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -72,15 +72,6 @@
"fan_min_speed": [
"10"
],
"filament_cooling_final_speed": [
"0"
],
"filament_cooling_initial_speed": [
"0"
],
"filament_cooling_moves": [
"0"
],
"filament_cost": [
"599"
],
@@ -99,30 +90,12 @@
"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": [
""
],
@@ -165,21 +138,6 @@
"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,15 +42,6 @@
"fan_min_speed": [
"20"
],
"filament_cooling_final_speed": [
"0"
],
"filament_cooling_initial_speed": [
"0"
],
"filament_cooling_moves": [
"0"
],
"filament_cost": [
"445"
],
@@ -69,30 +60,12 @@
"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": [
""
],
@@ -135,21 +108,6 @@
"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,15 +71,6 @@
"fan_min_speed": [
"60"
],
"filament_cooling_final_speed": [
"0"
],
"filament_cooling_initial_speed": [
"0"
],
"filament_cooling_moves": [
"0"
],
"filament_cost": [
"449.5"
],
@@ -98,30 +89,12 @@
"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": [
""
],
@@ -164,21 +137,6 @@
"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,15 +71,6 @@
"fan_min_speed": [
"100"
],
"filament_cooling_final_speed": [
"0"
],
"filament_cooling_initial_speed": [
"0"
],
"filament_cooling_moves": [
"0"
],
"filament_cost": [
"500"
],
@@ -98,30 +89,12 @@
"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": [
""
],
@@ -164,21 +137,6 @@
"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,6 +24,9 @@
"filament_max_volumetric_speed": [
"6"
],
"filament_multitool_ramming_flow": [
"6"
],
"nozzle_temperature": [
"290"
],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,6 +41,15 @@
"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,6 +66,12 @@
"filament_minimal_purge_on_wipe_tower": [
"15"
],
"filament_multitool_ramming": [
"1"
],
"filament_multitool_ramming_volume": [
"5"
],
"filament_retract_before_wipe": [
"nil"
],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -40,6 +40,15 @@
"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,6 +11,9 @@
"filament_max_volumetric_speed": [
"12"
],
"filament_multitool_ramming_flow": [
"12"
],
"filament_type": [
"SBS"
],

View File

@@ -43,6 +43,15 @@
"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,6 +17,9 @@
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"8"
],
"filament_vendor": [
"eSUN"
],

View File

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

View File

@@ -21,6 +21,9 @@
"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.07",
"version": "02.04.00.08",
"force_update": "0",
"description": "Snapmaker configurations",
"machine_model_list": [
@@ -426,10 +426,6 @@
"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"
@@ -486,6 +482,66 @@
"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"
@@ -1508,10 +1564,6 @@
"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"
@@ -1671,6 +1723,214 @@
{
"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

@@ -0,0 +1,32 @@
{
"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

@@ -0,0 +1,107 @@
{
"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

@@ -0,0 +1,32 @@
{
"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

@@ -0,0 +1,32 @@
{
"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,7 +11,6 @@
"filament_minimal_purge_on_wipe_tower": [
"15"
],
"pressure_advance": [
"0.05"
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
{
"type": "filament",
"from": "system",
"instantiation": "true",
"name": "PolyLite PLA @0.2 nozzle",
"setting_id": "s9mdMaFca8SHfji9",
"inherits": "PolyLite PLA @base",
"from": "system",
"setting_id": "s9mdMaFca8SHfji9",
"instantiation": "true",
"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