Compare commits

..
Author SHA1 Message Date
Hanif Koh 65e97cddd2 Ask Before Migrating User Filament Presets to Their Legacy Printers
Instead of giving user presets their parent's 2.4.2 printers in memory,
detect the user presets that inherit their parent's compatible_printers
and are missing printers from the legacy map, and offer to migrate them
at startup or from Help > Migrate Filament Presets.

Migrating first backs up the user folder to
filament_preset_migration_<datetime>_backup.zip in the configuration
folder, and migrates nothing if that fails. It then writes the printers
into each preset's compatible_printers, patching only that key so the
preset keeps the parent name it was saved with and older releases can
still resolve it. Cloud-synced presets are marked for upload.

No asks again at the next launch; "Don't remind me again" stops the
startup prompt.
2026-10-12 18:18:41 +08:00
Hanif Koh e55ac357b5 Keep User Filament Presets on Printers Their Parent Listed in 2.4.2
User presets store only their diff, so they inherit the parent's
compatible_printers. Profile changes since 2.4.2 renamed and narrowed many
system filament presets, so user presets made against them disappear from
printers they were made for and the saved selection silently falls back to
another filament at startup.

orca_profile_tool.py legacy-compat writes, per current system filament
preset, the printers it covered in an older release and no longer lists,
kept only where a preset with the same filament_id still covers the printer
and they share an extruder variant. The loader gives these printers to user
presets that inherit their parent's list, without writing any file; saving
a preset detached, cloned or with its own list drops them.
2026-10-12 17:16:58 +08:00
SoftFever d2675f15d1 Fix Qidi start G-code ignoring the selected plate type (#16372)
# Description

With "Support multi bed types" enabled on a Qidi printer, the
first-layer bed temperature was always taken from the Smooth PEI / High
Temp Plate setting, whichever plate type was selected.

The slicer computes the right value (`first_layer_bed_temperature` in
the G-code footer follows the selected plate), but the machine start
G-code of these presets hardcoded `[hot_plate_temp_initial_layer]`:

- `fdm_qidi_x3_common` (base of X-Max 3, X-Plus 3, X-Smart 3, X-Plus 4,
Q1 Pro)
- `Qidi Q1 Pro 0.4 nozzle`
- `Qidi X-CF Pro 0.4 nozzle`

They now use `[bed_temperature_initial_layer_single]`, which follows the
selected plate type, as most other profiles do. The Qidi bundle version
is bumped to `02.04.00.17`.

With "Support multi bed types" disabled, these printers use the default
plate (Smooth PEI / High Temp Plate), so the temperature is unchanged
there.

# Screenshots/Recordings/Graphs

N/A — profile change only.

## Tests

`python3 scripts/orca_profile_tool.py check` passes.

Sliced on Qidi X-Max 3 0.4 nozzle with "Support multi bed types"
enabled, `0.20mm Standard @Qidi XMax3`, `Generic PLA @Qidi`, with a
different bed temperature set for each plate type (first layer / other
layers).

Before, with first layer set to 50–55 °C: `M190 S54` for every plate
type.

After, a two-layer print:

| Plate type | Set (first / other) | `M190` at start | `M140` at layer 2
|
| --- | --- | --- | --- |
| Cool Plate SuperTack | 51 / 61 | 51 | 61 |
| Cool Plate | 52 / 62 | 52 | 62 |
| Textured Cool Plate | 53 / 63 | 53 | 63 |
| Engineering Plate | 54 / 64 | 54 | 64 |
| Smooth PEI / High Temp Plate | 55 / 65 | 55 | 65 |
| Textured PEI Plate | 56 / 66 | 56 | 66 |

🤖 Generated with [Claude Code](https://claude.com/claude-code)4
2026-10-12 17:14:22 +08:00
HanifKoh 5371c85362 Refresh Dark Mode Colours When the System Theme Differs at Startup (#16390)
On Linux and macOS the app follows the system theme: at startup it
overwrites the saved dark_color_mode with the system appearance. The label
colours and the cached dark mode flag were already set from the saved value
by then, and the later "changed" check reads the mode only after the
overwrite, so it never fires. For that session every dialog that maps its
colours through UpdateDlgDarkUI keeps the old mode, e.g. white dialogs on a
dark main window after switching the desktop to dark, or on a fresh install
on a dark desktop.

Refresh the label colours and the flag when the system theme differs from
the saved mode.
2026-10-12 17:09:05 +08:00
SoftFever a243941b72 Add favorite filaments to AMS filament dropdown (#13946)
# Description

## Summary:

Adds favorite filament support to the AMS material selection dropdown.

Users can now click a star icon next to a selectable filament in the AMS Materials Setting dialog to mark it as a favorite. Favorited filaments appear in a new `Favorite filaments` section at the top of the dropdown, while all compatible filaments remain available in the `All filaments` section.

This PR also adds generic right-side action icon support to Orca's custom dropdown widget:

- Adds optional action icons to `DropDown::Item`.
- Adds a new `EVT_DROPDOWN_ITEM_ACTION` event for clicks on the action icon.
- Relays the action event through `ComboBox`.
- Adds `ComboBox::RefreshDropdownIfOpen()` so callers can refresh an already-open dropdown without changing normal `ForceDropdownOpen()` behavior.

The AMS favorites implementation:

- Uses existing bundled star assets:
  - `score_star_dark` for non-favorited filaments
  - `score_star_light` for favorited filaments
- Stores favorite filament IDs in app config under `ams_favorite_filament_ids`.
- Stores stable filament IDs instead of display names
- Uses existing C-style string escaping helpers for persistence.
- Keeps Bambu/RFID/read-only slot behavior unchanged

No new external dependencies.

**Note:** `AMSMaterialsSetting` now uses Orca's custom `ComboBox` on macOS as well, because the favorite-star UI depends on the custom `DropDown` item renderer. I was able to test this on Windows, but macOS/Linux smoke testing is still needed and would be appreciated.

# Screenshots/Recordings:

https://github.com/user-attachments/assets/41e881be-d4de-4a7d-8f99-4297567adef2

<img width="494" height="36" alt="Screenshot 2026-05-30 103432" src="https://github.com/user-attachments/assets/e7c34ed3-db7a-4705-a1cd-4bfe7ac1616a" />


## Tests

Tested on Windows:

- Built OrcaSlicer locally
- Opened the Device tab with Bambu Lab printer with AMS
- Opened the AMS Materials Setting dialog from an editable AMS slot
- Confirmed clicking a star toggles favorite state.
- Star clicks do not select the filament row.
- Dropdown remains open after toggling a favorite.
- Confirmed favorited filaments appear under `Favorite filaments`.
- All compatible filaments remain visible under `All filaments`.
- Confirmed Filament still selected as before
- Confirmed favorites persist after closing and reopening the dialog.
- Confirmed favorites persist after restarting OrcaSlicer.
- Confirmed the favorite IDs are saved in `OrcaSlicer.conf` under `ams_favorite_filament_ids`.
- Bambu/RFID/read-only filament slots continue using the existing read-only display behavior.

Not tested:

- macOS
- Linux/Wayland

These should be tested, `ComboBox` behavior not tested on macOS.

[How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
2026-10-12 16:37:29 +08:00
SoftFever 6a1a5d51e9 Show an outline star for filaments that are not favorites 2026-10-12 16:03:49 +08:00
HanifKoh e625845b34 Keep Snapshot Suppression for the Whole Object List Edit (#16388)
Five Plater::TakeSnapshot guards in GUI_ObjectList were unnamed temporaries, so snapshots were allowed again as soon as the snapshot was taken. Naming them keeps the suppression until the edit is done, as everywhere else.
2026-10-12 16:02:43 +08:00
SoftFever 01b340dee1 Fix regressions in the AMS filament favorites dropdown 2026-10-12 14:54:17 +08:00
HanifKoh 4c2a63e11e Copy Plate Pick and Pattern File Paths When Loading a G-code 3MF (#16387)
The importer assigned pick_file.empty() and pattern_bbox_file.empty(), a bool, to the string fields, storing a one-character string instead of the path.
2026-10-12 14:14:28 +08:00
HanifKoh f8cf34c55b Reset the Fan Mover Instead of Releasing It (#16386)
Clearing per-export state called unique_ptr::release(), which gives up ownership without deleting. Every export uses a new GCode, so the pointer is still empty there today and nothing leaks; reset() states the intent and would free the object if a GCode were ever reused.
2026-10-12 13:42:19 +08:00
SoftFever d007134001 fix: update translation for Assistant(HMS) in Chinese localization 2026-10-12 12:53:16 +08:00
SoftFever 00d11a7ba0 Merge branch 'main' into UI-changes 2026-10-12 12:17:21 +08:00
Boris SamorodovandClaude Opus 5.5 546b9c621d Fix Qidi start G-code ignoring the selected plate type
The start G-code of fdm_qidi_x3_common, Qidi Q1 Pro 0.4 nozzle and
Qidi X-CF Pro 0.4 nozzle heated the bed to hot_plate_temp_initial_layer,
so with "Support multi bed types" enabled every plate type got the
Smooth PEI / High Temp Plate temperature. Use
bed_temperature_initial_layer_single, which follows the selected plate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:49:25 +02:00
SoftFever a13089d357 Merge branch 'main' into UI-changes 2026-06-02 14:40:05 +08:00
KrishManan fb6c93963d Add AMS filament favorites 2026-05-30 17:45:03 +08:00
KrishManan 7688f7a16c Add dropdown item action icons 2026-05-30 17:45:03 +08:00
40 changed files with 1946 additions and 198 deletions
@@ -569,16 +569,12 @@ on the **first** call. So:
#endif
```
**Before GTK starts**, only the environment exists. `CLI::run` (`src/OrcaSlicer.cpp`) sets the NVIDIA
DMABUF workaround and then branches on `GDK_BACKEND` (an `x11` prefix means the X11 opt-in). On both
paths, when `/proc/driver/nvidia/version` exists and the linked WebKitGTK honours the switch (>= 2.44,
`WebView::SupportsForceSharedMemory()`), it sets `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and
`WEBKIT_FORCE_DMABUF_RENDERER=1` (non-replacing): the proprietary driver cannot back WebKit's hardware
DMABUF renderer. On the default path it forces `GDK_BACKEND=x11` when wx lacks EGL and `WAYLAND_DISPLAY`
is set. It sets `WEBKIT_DISABLE_COMPOSITING_MODE=1` (non-replacing) only when both `DISPLAY` and
`WAYLAND_DISPLAY` are set and the linked WebKitGTK is older than 2.42, and calls `XInitThreads()`
**Before GTK starts**, only the environment exists. `CLI::run` (`src/OrcaSlicer.cpp`) branches on
`GDK_BACKEND` (an `x11` prefix means the X11 opt-in). On the default path it forces `GDK_BACKEND=x11`
when wx lacks EGL and `WAYLAND_DISPLAY` is set. It sets `WEBKIT_DISABLE_COMPOSITING_MODE=1`
(non-replacing) only when both `DISPLAY` and `WAYLAND_DISPLAY` are set, and calls `XInitThreads()`
only when `DISPLAY` is set. With neither variable set, the GUI refuses to start ("Neither DISPLAY nor
WAYLAND_DISPLAY set"). The details and the WebKit rules (c12912e0df) are in
WAYLAND_DISPLAY set"). The details and the WebKit rule (c12912e0df) are in
`references/webview-gl-aui-media.md` §WebKitGTK on Linux sessions.
**wx's own detectors**, for reference:
@@ -61,8 +61,7 @@ available; GTK2/WebKit1 is an opt-out configuration the GUI does not support.
11. A web host that re-themes in place handles `EVT_WEBVIEW_RECREATED` without `Skip()`; one that
needs a reload lets it through. §[Orca wrapper](#orca-webview-wrapper-widgetswebview)
12. Keep `WebViewWebKit`'s destructor removing the `"wx"` handler. §[Orca wrapper](#orca-webview-wrapper-widgetswebview)
13. Do not widen `WEBKIT_DISABLE_COMPOSITING_MODE` beyond Orca's default-path XWayland case for
WebKitGTK older than 2.42, and never
13. Do not widen `WEBKIT_DISABLE_COMPOSITING_MODE` beyond Orca's default-path XWayland case, and never
make a page's progress depend only on a C++→JS callback. §[WebKitGTK on Linux](#webkitgtk-on-linux-sessions)
14. GL attribute lists: legacy `int[]` lists end with `0` and spell out `WX_GL_RGBA` and
`WX_GL_DOUBLEBUFFER`; `wxGLAttributes`/`wxGLContextAttrs` end with `EndList()`; MSAA is requested
@@ -568,66 +567,33 @@ vue-resize workaround script and sets a persistent SQLite cookie store on the vi
## WebKitGTK on Linux sessions
**Mechanism.** `CLI::run` (`src/OrcaSlicer.cpp`, under `#ifdef __WXGTK__`, before GTK initialises)
sets the NVIDIA DMABUF workaround and then branches on `GDK_BACKEND`:
- **Both paths**, before the split: when `/proc/driver/nvidia/version` exists and the linked WebKitGTK
honours the switch (>= 2.44, `WebView::SupportsForceSharedMemory()`), it sets
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and `WEBKIT_FORCE_DMABUF_RENDERER=1` non-replacing. The
proprietary driver cannot back WebKit's hardware DMABUF renderer, so the shared-memory transport keeps
accelerated compositing instead of dropping it; `WEBKIT_FORCE_DMABUF_RENDERER` clears the
Debian/Ubuntu/openSUSE `isNVIDIA()` early return, which otherwise returns before the FORCE_SHM check.
branches on `GDK_BACKEND`:
- **Default path** (`GDK_BACKEND` unset or not starting with `x11`): if wx lacks EGL (`!wxHAS_EGL`) and
`WAYLAND_DISPLAY` is set, it forces `GDK_BACKEND=x11`; it sets `WEBKIT_DISABLE_COMPOSITING_MODE=1`
non-replacing **only** when the linked WebKitGTK predates 2.42 (`WebView::NeedsCompositingWorkaround()`)
**and** both `DISPLAY` and `WAYLAND_DISPLAY` are non-empty (a Wayland session with
non-replacing **only** when both `DISPLAY` and `WAYLAND_DISPLAY` are non-empty (a Wayland session with
XWayland available — the session, not the backend GTK will pick); `XInitThreads()` runs only when
`DISPLAY` is set.
- **`GDK_BACKEND=x11…` opt-in path**: sets `DRI_PRIME=1` (and the NVIDIA PRIME variables when
`/proc/driver/nvidia/version` exists), calls `XInitThreads()`, and deliberately does **not** disable
compositing, which keeps hardware acceleration for Device / Setup Wizard / login pages.
compositing (the comment: the WebKit2GTK XWayland bug appears fixed in WebKit2GTK ≥ 2.42, and leaving
compositing on keeps hardware acceleration for Device / Setup Wizard / login pages).
Runtime backend detection elsewhere uses Orca's `is_running_on_wayland()` / `is_running_on_x11()`
(see `references/platforms.md`), not these environment variables.
**Pitfall.**
- **Rule:** On NVIDIA reach for `WEBKIT_DMABUF_RENDERER_FORCE_SHM` before
`WEBKIT_DISABLE_COMPOSITING_MODE`. **Why:** the proprietary driver cannot back WebKit's hardware
DMABUF renderer, leaving web views blank or slow; FORCE_SHM keeps accelerated compositing and only
skips the hardware path, while disabling compositing drops it. Threaded scrolling is a separate,
release-dependent default (off in 2.44/2.45, on from 2.46). `WEBKIT_DISABLE_DMABUF_RENDERER` is not a
substitute — it is read first, leaves the transport set empty (no compositing from 2.44 on, where the
X11/Wayland fallback renderers were removed) and has a reported crash path. On Debian/Ubuntu and
openSUSE the `isNVIDIA()` patch returns before the FORCE_SHM check, so `WEBKIT_FORCE_DMABUF_RENDERER=1`
must clear it first; that variable exists only in those patches. WebKit reads all of these once per
process, before the first WebView.
The test is `/proc/driver/nvidia/version`, so a hybrid laptop that renders on its iGPU also takes the
shared-memory path, which is a per-frame GPU readback rather than zero-copy; exporting
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=0` in the environment restores the hardware path.
```cpp
// Wrong: kills compositing and threaded scrolling (and can crash on modern WebKit)
::setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1", false);
// Right: keep compositing, skip only the hardware DMABUF path
if (::access("/proc/driver/nvidia/version", F_OK) == 0 && WebView::SupportsForceSharedMemory()) {
::setenv("WEBKIT_DMABUF_RENDERER_FORCE_SHM", "1", /*replace*/ false);
::setenv("WEBKIT_FORCE_DMABUF_RENDERER", "1", /*replace*/ false);
}
```
Cite: WebKit 2.52.6 `Source/WebKit/UIProcess/gtk/AcceleratedBackingStore.cpp`
(`rendererBufferTransportMode()`), Debian `disable-nvidia-dmabuf.patch`, WebKit bug 262607.
- **Rule:** Set `WEBKIT_DISABLE_COMPOSITING_MODE` only on the default path in Wayland sessions,
non-replacing (`setenv(..., false)`, so a value the user already exported wins), before GTK
initialises; never unconditionally, and only for the WebKitGTK releases that need it (older than
2.42, `WebView::NeedsCompositingWorkaround()`). Keep JS-side fallbacks so a page never
initialises; never unconditionally. Keep JS-side fallbacks so a page never
waits forever for a C++→JS call.
**Why:** WebKit2GTK accelerated compositing could fail silently under XWayland before 2.42, leaving
web views blank (Setup Wizard stuck on "Loading..."); the variable also turns off threaded scrolling,
so applying it on a newer WebKit needlessly drops both GPU compositing and async scrolling (the
Device tab loses its smoothness); without the JS timeout the user is stuck when the signal is lost.
**Why:** WebKit2GTK accelerated compositing could fail silently under XWayland, leaving web views
blank (Setup Wizard stuck on "Loading..."); disabling it everywhere needlessly drops hardware
acceleration; without the JS timeout the user is stuck when the signal is lost.
```cpp
// Wrong: unconditional, kills HW acceleration everywhere
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", false);
// Right (default path only): non-replacing, before GTK init, WebKitGTK < 2.42,
// Wayland session with XWayland
if (WebView::NeedsCompositingWorkaround() && display && *display && wayland && *wayland)
// Right (default path only): non-replacing, before GTK init, Wayland session with XWayland
if (display && *display && wayland && *wayland)
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /*replace*/ false);
```
Cite: c12912e0df (`src/OrcaSlicer.cpp`; `resources/web/guide/0/load.js` `OnInit`
+1
View File
@@ -309,6 +309,7 @@ src/slic3r/GUI/BBLStatusBarPrint.cpp
src/slic3r/GUI/BBLStatusBarSend.cpp
src/slic3r/GUI/BaseTransparentDPIFrame.cpp
src/slic3r/GUI/CloneDialog.cpp
src/slic3r/GUI/LegacyPrinterMigrationDialog.cpp
src/slic3r/GUI/ColorDecomposeDialog.cpp
src/slic3r/GUI/ColorDecomposeSupport.cpp
src/slic3r/GUI/ConfigWizard.cpp
+1 -1
View File
@@ -7478,7 +7478,7 @@ msgid "Update"
msgstr "更新"
msgid "Assistant(HMS)"
msgstr "助理(HMS)"
msgstr "助手(HMS)"
#, c-format, boost-format
msgid "Network plug-in v%s"
+3
View File
@@ -0,0 +1,3 @@
<svg width="28" height="27" viewBox="0 0 28 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path transform="matrix(0.9 0 0 0.9 1.4 1.35)" d="M11.9888 2.01601C12.8984 0.494662 15.1024 0.494662 16.012 2.01601L18.8677 6.79237C19.1958 7.34113 19.7339 7.73201 20.3572 7.87447L25.7821 9.11446C27.5101 9.50943 28.1913 11.6056 27.0254 12.9408L23.3652 17.1327C22.9449 17.6143 22.7394 18.2469 22.7964 18.8836L23.2936 24.4263C23.4519 26.1918 21.6687 27.4873 20.0386 26.7911L14.9209 24.6056C14.3329 24.3545 13.6679 24.3545 13.0799 24.6056L7.96209 26.7911C6.33196 27.4873 4.54887 26.1918 4.70722 24.4263L5.20434 18.8836C5.26145 18.2469 5.05595 17.6143 4.63544 17.1327L0.97533 12.9408C-0.19048 11.6056 0.490599 9.50943 2.21859 9.11446L7.64364 7.87447C8.2669 7.73201 8.80494 7.34113 9.13302 6.79237L11.9888 2.01601Z" stroke="#949494" stroke-width="2.4" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 876 B

@@ -0,0 +1,795 @@
{
"presets": {
"Bambu ABS @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu ABS @BBL X1C": [
"Bambu Lab P1S 0.4 nozzle",
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu ASA @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu PA6-CF @BBL H2D": [
"Bambu Lab H2D 0.6 nozzle",
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu PA6-CF @BBL H2DP": [
"Bambu Lab H2D Pro 0.6 nozzle",
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu PA6-CF @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu PA6-CF @BBL P2S": [
"Bambu Lab P2S 0.6 nozzle",
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu PA6-CF @BBL X1C": [
"Bambu Lab P1P 0.6 nozzle",
"Bambu Lab P1P 0.8 nozzle",
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab P1S 0.8 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle",
"Bambu Lab X1 Carbon 0.8 nozzle"
],
"Bambu PA6-CF @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu PAHT-CF @BBL H2D": [
"Bambu Lab H2D 0.6 nozzle",
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu PAHT-CF @BBL H2DP": [
"Bambu Lab H2D Pro 0.6 nozzle",
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu PAHT-CF @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu PAHT-CF @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle",
"Bambu Lab P1P 0.8 nozzle"
],
"Bambu PAHT-CF @BBL P2S": [
"Bambu Lab P2S 0.6 nozzle",
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu PAHT-CF @BBL X1C": [
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab P1S 0.8 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle",
"Bambu Lab X1 Carbon 0.8 nozzle"
],
"Bambu PAHT-CF @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu PC @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PETG @0.2 nozzle": [
"Qidi Q1 Pro 0.2 nozzle"
],
"Bambu PETG Basic @BBL H2D 0.6 nozzle": [
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu PETG Basic @BBL H2DP 0.6 nozzle": [
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu PETG Basic @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu PETG Basic @BBL P2S 0.6 nozzle": [
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu PETG Basic @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu PETG HF @BBL P2S 0.6 nozzle": [
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu PETG HF @BBL X1C": [
"Bambu Lab P1P 0.4 nozzle",
"Bambu Lab P1S 0.4 nozzle"
],
"Bambu PETG HF @BBL X1C 0.8 nozzle": [
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PETG HF @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu PETG Translucent @BBL H2D 0.6 nozzle": [
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu PETG Translucent @BBL H2DP 0.6 nozzle": [
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu PETG Translucent @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu PETG Translucent @BBL P2S 0.6 nozzle": [
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu PETG Translucent @BBL X1C 0.8 nozzle": [
"Bambu Lab P1P 0.6 nozzle",
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PETG-CF @BBL H2D 0.6 nozzle": [
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu PETG-CF @BBL H2DP 0.6 nozzle": [
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu PETG-CF @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu PETG-CF @BBL P2S 0.6 nozzle": [
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu PLA Basic @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PLA Basic @BBL X1C": [
"Bambu Lab P1S 0.4 nozzle",
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA Basic @BBL X2D 0.6 nozzle": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu PLA Dynamic @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PLA Dynamic @BBL X1C": [
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA Galaxy @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PLA Galaxy @BBL X1C": [
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA Glow @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PLA Glow @BBL X1C": [
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA Lite @BBL H2D": [
"Bambu Lab H2D 0.6 nozzle"
],
"Bambu PLA Lite @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu PLA Lite @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PLA Lite @BBL X1C": [
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA Lite @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu PLA Marble @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle",
"Bambu Lab P1P 0.8 nozzle"
],
"Bambu PLA Marble @BBL X1C": [
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA Matte @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PLA Matte @BBL X1C": [
"Bambu Lab P1S 0.4 nozzle",
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA Matte @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu PLA Metal @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PLA Metal @BBL X1C": [
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA Silk+ @BBL H2D": [
"Bambu Lab H2D 0.6 nozzle",
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu PLA Silk+ @BBL H2DP": [
"Bambu Lab H2D Pro 0.6 nozzle",
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu PLA Silk+ @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu PLA Silk+ @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PLA Silk+ @BBL X1C": [
"Bambu Lab P1S 0.4 nozzle",
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA Silk+ @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu PLA Sparkle @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PLA Sparkle @BBL X1C": [
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA Tough+ @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PLA Tough+ @BBL X1C": [
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA Tough+ @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu PLA Translucent @BBL H2D 0.8 nozzle": [
"Bambu Lab H2D 0.6 nozzle"
],
"Bambu PLA Translucent @BBL H2DP 0.8 nozzle": [
"Bambu Lab H2D Pro 0.6 nozzle"
],
"Bambu PLA Translucent @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu PLA Wood @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu PLA Wood @BBL X1C": [
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu PLA-CF @BBL H2D 0.6 nozzle": [
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu PLA-CF @BBL H2DP 0.6 nozzle": [
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu PLA-CF @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu PLA-CF @BBL P2S 0.6 nozzle": [
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu PLA-CF @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu PPA-CF @BBL X1C": [
"Bambu Lab P1P 0.6 nozzle",
"Bambu Lab P1P 0.8 nozzle",
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab P1S 0.8 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle",
"Bambu Lab X1 Carbon 0.8 nozzle"
],
"Bambu PVA @BBL H2D": [
"Bambu Lab H2D 0.6 nozzle",
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu PVA @BBL H2DP": [
"Bambu Lab H2D Pro 0.6 nozzle",
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu PVA @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu PVA @BBL P2S": [
"Bambu Lab P2S 0.6 nozzle",
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu PVA @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu Support For PLA @BBL H2D": [
"Bambu Lab H2D 0.6 nozzle",
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu Support For PLA @BBL H2DP": [
"Bambu Lab H2D Pro 0.6 nozzle",
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu Support For PLA @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu Support For PLA @BBL P2S": [
"Bambu Lab P2S 0.6 nozzle",
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu Support For PLA @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu Support For PLA/PETG @BBL H2D": [
"Bambu Lab H2D 0.6 nozzle",
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu Support For PLA/PETG @BBL H2DP": [
"Bambu Lab H2D Pro 0.6 nozzle",
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu Support For PLA/PETG @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu Support For PLA/PETG @BBL P2S": [
"Bambu Lab P2S 0.6 nozzle",
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu Support For PLA/PETG @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu Support for ABS @BBL H2D": [
"Bambu Lab H2D 0.6 nozzle",
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu Support for ABS @BBL H2DP": [
"Bambu Lab H2D Pro 0.6 nozzle",
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu Support for ABS @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu Support for ABS @BBL P2S": [
"Bambu Lab P2S 0.6 nozzle",
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu Support for ABS @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu TPU 90A @BBL H2D": [
"Bambu Lab H2D 0.6 nozzle",
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu TPU 90A @BBL H2DP": [
"Bambu Lab H2D Pro 0.6 nozzle",
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu TPU 95A HF @BBL H2D": [
"Bambu Lab H2D 0.4 nozzle",
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu TPU 95A HF @BBL H2DP": [
"Bambu Lab H2D Pro 0.4 nozzle",
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu TPU 95A HF @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu TPU 95A HF @BBL P2S": [
"Bambu Lab P2S 0.6 nozzle",
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu TPU 95A HF @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Bambu TPU for AMS @BBL H2D": [
"Bambu Lab H2D 0.6 nozzle",
"Bambu Lab H2D 0.8 nozzle"
],
"Bambu TPU for AMS @BBL H2DP": [
"Bambu Lab H2D Pro 0.6 nozzle",
"Bambu Lab H2D Pro 0.8 nozzle"
],
"Bambu TPU for AMS @BBL H2S": [
"Bambu Lab H2S 0.6 nozzle",
"Bambu Lab H2S 0.8 nozzle"
],
"Bambu TPU for AMS @BBL P1P": [
"Bambu Lab P1P 0.6 nozzle"
],
"Bambu TPU for AMS @BBL P2S": [
"Bambu Lab P2S 0.6 nozzle",
"Bambu Lab P2S 0.8 nozzle"
],
"Bambu TPU for AMS @BBL X1C": [
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 Carbon 0.6 nozzle"
],
"Bambu TPU for AMS @BBL X2D": [
"Bambu Lab X2D 0.8 nozzle"
],
"Blocks ABS @Blocks_RD50_V2": [
"BLOCKS RF50 0.4 nozzle",
"BLOCKS RF50 0.6 nozzle",
"BLOCKS RF50 0.8 nozzle"
],
"Blocks ASA @Blocks_RD50_V2": [
"BLOCKS RF50 0.4 nozzle",
"BLOCKS RF50 0.6 nozzle",
"BLOCKS RF50 0.8 nozzle"
],
"Blocks ASA-CF @Blocks_RD50_V2": [
"BLOCKS RF50 0.4 nozzle",
"BLOCKS RF50 0.6 nozzle",
"BLOCKS RF50 0.8 nozzle"
],
"Blocks PA @Blocks_RD50_V2": [
"BLOCKS RF50 0.4 nozzle",
"BLOCKS RF50 0.6 nozzle",
"BLOCKS RF50 0.8 nozzle"
],
"Blocks PA-CF @Blocks_RD50_V2": [
"BLOCKS RF50 0.4 nozzle",
"BLOCKS RF50 0.6 nozzle",
"BLOCKS RF50 0.8 nozzle"
],
"Blocks PC @Blocks_RD50_V2": [
"BLOCKS RF50 0.4 nozzle",
"BLOCKS RF50 0.6 nozzle",
"BLOCKS RF50 0.8 nozzle"
],
"Blocks PETG @Blocks_Pro_S100": [
"BLOCKS RD50 V2 0.4 nozzle",
"BLOCKS RD50 V2 0.6 nozzle",
"BLOCKS RD50 V2 0.8 nozzle",
"BLOCKS RF50 0.4 nozzle",
"BLOCKS RF50 0.6 nozzle",
"BLOCKS RF50 0.8 nozzle"
],
"Blocks PLA @Blocks_Pro_S100": [
"BLOCKS RD50 V2 0.4 nozzle",
"BLOCKS RD50 V2 0.6 nozzle",
"BLOCKS RD50 V2 0.8 nozzle",
"BLOCKS RF50 0.4 nozzle",
"BLOCKS RF50 0.6 nozzle",
"BLOCKS RF50 0.8 nozzle"
],
"Blocks PLA-CF @Blocks_RD50_V2": [
"BLOCKS RF50 0.4 nozzle",
"BLOCKS RF50 0.6 nozzle",
"BLOCKS RF50 0.8 nozzle"
],
"Blocks PVA @Blocks_RD50_V2": [
"BLOCKS RF50 0.4 nozzle",
"BLOCKS RF50 0.6 nozzle",
"BLOCKS RF50 0.8 nozzle"
],
"Blocks TPU @Blocks_Pro_S100": [
"BLOCKS RD50 V2 0.4 nozzle",
"BLOCKS RD50 V2 0.6 nozzle",
"BLOCKS RD50 V2 0.8 nozzle",
"BLOCKS RF50 0.4 nozzle",
"BLOCKS RF50 0.6 nozzle",
"BLOCKS RF50 0.8 nozzle"
],
"Cubicon ABS @Cubicon xCeler-I 0.4 nozzle": [
"Cubicon xCeler-Plus 0.4 nozzle"
],
"Cubicon ABS-A100 @Cubicon xCeler-I 0.4 nozzle": [
"Cubicon xCeler-Plus 0.4 nozzle"
],
"Cubicon ABSk @Cubicon xCeler-I 0.4 nozzle": [
"Cubicon xCeler-Plus 0.4 nozzle"
],
"Cubicon PA-CF @Cubicon xCeler-I 0.4 nozzle": [
"Cubicon xCeler-Plus 0.4 nozzle"
],
"Cubicon PC @Cubicon xCeler-I 0.4 nozzle": [
"Cubicon xCeler-Plus 0.4 nozzle"
],
"Cubicon PETG @Cubicon xCeler-I 0.4 nozzle": [
"Cubicon xCeler-Plus 0.4 nozzle"
],
"Cubicon PLA @Cubicon xCeler-I 0.4 nozzle": [
"Cubicon xCeler-Plus 0.4 nozzle"
],
"Cubicon PLA+ @Cubicon xCeler-I 0.4 nozzle": [
"Cubicon xCeler-Plus 0.4 nozzle"
],
"Cubicon PLAi21 @Cubicon xCeler-I 0.4 nozzle": [
"Cubicon xCeler-Plus 0.4 nozzle"
],
"Flashforge HS PLA": [
"Flashforge AD5X 0.4 nozzle",
"Flashforge AD5X 0.6 nozzle",
"Flashforge AD5X 0.8 nozzle"
],
"Flashforge PETG-CF": [
"Flashforge AD5X 0.4 nozzle",
"Flashforge AD5X 0.6 nozzle",
"Flashforge AD5X 0.8 nozzle"
],
"Flashforge PLA-CF": [
"Flashforge AD5X 0.4 nozzle",
"Flashforge AD5X 0.6 nozzle",
"Flashforge AD5X 0.8 nozzle"
],
"Flashforge TPU 95A": [
"Flashforge AD5X 0.4 nozzle",
"Flashforge AD5X 0.6 nozzle",
"Flashforge AD5X 0.8 nozzle"
],
"FusRock PAHT-CF": [
"Flashforge Guider 3 Ultra 0.6 Nozzle"
],
"FusRock PET-CF": [
"Flashforge Guider 3 Ultra 0.6 Nozzle"
],
"Generic ABS @Anycubic": [
"Anycubic Kobra 3 0.4 nozzle"
],
"Generic ABS @Creality": [
"Creality CR-10 SE 0.2 nozzle",
"Creality CR-10 SE 0.4 nozzle",
"Creality CR-10 SE 0.6 nozzle",
"Creality CR-10 SE 0.8 nozzle",
"Creality CR-6 SE 0.2 nozzle",
"Creality CR-6 SE 0.4 nozzle",
"Creality CR-6 SE 0.6 nozzle",
"Creality CR-6 SE 0.8 nozzle",
"Creality CR-M4 0.4 nozzle",
"Creality Ender-3 V2 0.4 nozzle"
],
"Generic ABS @Creality Ender-3V3-all": [
"Creality Ender-3 V3 Plus 0.4 nozzle",
"Creality Ender-3 V3 Plus 0.6 nozzle"
],
"Generic ABS @Creality Hi-all": [
"Creality Hi 0.4 nozzle"
],
"Generic ABS @Creality K1-all": [
"Creality K1 SE 0.4 nozzle",
"Creality K1C 0.4 nozzle"
],
"Generic ABS @Creality K2-all": [
"Creality K2 0.4 nozzle",
"Creality K2 Plus 0.4 nozzle",
"Creality K2 Plus 0.6 nozzle",
"Creality K2 Plus 0.8 nozzle",
"Creality K2 Pro 0.4 nozzle"
],
"Generic ABS @Flashforge G3U": [
"Flashforge Guider 3 Ultra 0.6 Nozzle"
],
"Generic ASA @Creality Ender-3V3-all": [
"Creality Ender-3 V3 Plus 0.4 nozzle",
"Creality Ender-3 V3 Plus 0.6 nozzle"
],
"Generic ASA @Creality K1-all": [
"Creality K1 SE 0.4 nozzle",
"Creality K1C 0.4 nozzle"
],
"Generic ASA @Creality K2-all": [
"Creality K2 0.4 nozzle",
"Creality K2 Plus 0.4 nozzle",
"Creality K2 Pro 0.4 nozzle"
],
"Generic ASA @Flashforge G3U": [
"Flashforge Guider 3 Ultra 0.6 Nozzle"
],
"Generic HIPS @Flashforge": [
"Flashforge Guider 3 Ultra 0.6 Nozzle"
],
"Generic PA @Prusa": [
"Prusa MINIIS 0.25 nozzle",
"Prusa MINIIS 0.4 nozzle",
"Prusa MINIIS 0.6 nozzle",
"Prusa MINIIS 0.8 nozzle"
],
"Generic PA-CF @Creality Ender-3V3-all": [
"Creality Ender-3 V3 Plus 0.4 nozzle",
"Creality Ender-3 V3 Plus 0.6 nozzle"
],
"Generic PA-CF @Creality K1-all": [
"Creality K1C 0.4 nozzle"
],
"Generic PA-CF @Creality K2-all": [
"Creality K2 Plus 0.4 nozzle",
"Creality K2 Plus 0.6 nozzle",
"Creality K2 Plus 0.8 nozzle"
],
"Generic PA-CF @Prusa": [
"Prusa MINIIS 0.25 nozzle",
"Prusa MINIIS 0.4 nozzle",
"Prusa MINIIS 0.6 nozzle",
"Prusa MINIIS 0.8 nozzle"
],
"Generic PC @Creality K1-all": [
"Creality K1C 0.4 nozzle"
],
"Generic PETG @Creality": [
"Creality CR-6 SE 0.2 nozzle",
"Creality CR-6 SE 0.4 nozzle",
"Creality CR-6 SE 0.6 nozzle",
"Creality CR-6 SE 0.8 nozzle",
"Creality CR-M4 0.4 nozzle"
],
"Generic PETG @Creality Ender-3V3-all": [
"Creality Ender-3 V3 0.4 nozzle",
"Creality Ender-3 V3 0.6 nozzle",
"Creality Ender-3 V3 Plus 0.4 nozzle",
"Creality Ender-3 V3 Plus 0.6 nozzle",
"Creality Ender-3 V3 SE 0.2 nozzle",
"Creality Ender-3 V3 SE 0.4 nozzle",
"Creality Ender-3 V3 SE 0.6 nozzle",
"Creality Ender-3 V3 SE 0.8 nozzle"
],
"Generic PETG @Creality Hi-all": [
"Creality Hi 0.4 nozzle"
],
"Generic PETG @Creality K1-all": [
"Creality K1 SE 0.4 nozzle",
"Creality K1C 0.4 nozzle"
],
"Generic PETG @Creality K2-all": [
"Creality K2 0.4 nozzle",
"Creality K2 Plus 0.4 nozzle",
"Creality K2 Plus 0.6 nozzle",
"Creality K2 Plus 0.8 nozzle",
"Creality K2 Pro 0.4 nozzle"
],
"Generic PETG @Flashforge G3U": [
"Flashforge Guider 3 Ultra 0.6 Nozzle",
"Flashforge Guider 3 Ultra 0.8 Nozzle"
],
"Generic PETG @Sovol Zero Hardened Steel nozzle": [
"Sovol Zero 0.4 nozzle"
],
"Generic PETG HF @BBL X1C": [
"Bambu Lab P1S 0.4 nozzle",
"Bambu Lab P1S 0.6 nozzle",
"Bambu Lab X1 0.4 nozzle",
"Bambu Lab X1 0.6 nozzle",
"Bambu Lab X1 0.8 nozzle"
],
"Generic PETG-CF @Flashforge G3U": [
"Flashforge Guider 3 Ultra 0.6 Nozzle",
"Flashforge Guider 3 Ultra 0.8 Nozzle"
],
"Generic PLA @Creality": [
"Creality CR-10 SE 0.2 nozzle",
"Creality CR-10 SE 0.4 nozzle",
"Creality CR-10 SE 0.6 nozzle",
"Creality CR-10 SE 0.8 nozzle",
"Creality CR-6 SE 0.2 nozzle",
"Creality CR-6 SE 0.4 nozzle",
"Creality CR-6 SE 0.6 nozzle",
"Creality CR-6 SE 0.8 nozzle",
"Creality CR-M4 0.4 nozzle"
],
"Generic PLA @Creality Ender-3V3-all": [
"Creality Ender-3 V3 0.4 nozzle",
"Creality Ender-3 V3 0.6 nozzle",
"Creality Ender-3 V3 Plus 0.4 nozzle",
"Creality Ender-3 V3 Plus 0.6 nozzle"
],
"Generic PLA @Creality K1-all": [
"Creality K1 SE 0.4 nozzle",
"Creality K1C 0.4 nozzle"
],
"Generic PLA @Creality K2-all": [
"Creality K2 0.4 nozzle",
"Creality K2 Plus 0.4 nozzle",
"Creality K2 Plus 0.6 nozzle",
"Creality K2 Plus 0.8 nozzle",
"Creality K2 Pro 0.4 nozzle"
],
"Generic PLA @Flashforge G3U": [
"Flashforge Guider 3 Ultra 0.6 Nozzle",
"Flashforge Guider 3 Ultra 0.8 Nozzle"
],
"Generic PLA @Hi-all": [
"Creality Hi 0.4 nozzle",
"Creality Hi 0.6 nozzle"
],
"Generic PLA @Sovol Zero Hardened Steel nozzle": [
"Sovol Zero 0.4 nozzle"
],
"Generic PLA Silk @Sovol Zero Hardened Steel nozzle": [
"Sovol Zero 0.4 nozzle"
],
"Generic PLA-CF @Creality K1-all": [
"Creality K1 SE 0.4 nozzle",
"Creality K1C 0.4 nozzle"
],
"Generic PLA-CF @Creality K2-all": [
"Creality K2 0.4 nozzle",
"Creality K2 Plus 0.4 nozzle",
"Creality K2 Pro 0.4 nozzle"
],
"Generic PLA-CF @Flashforge G3U": [
"Flashforge Guider 3 Ultra 0.6 Nozzle",
"Flashforge Guider 3 Ultra 0.8 Nozzle"
],
"Generic PLA-CF @Prusa": [
"Prusa MINIIS 0.25 nozzle",
"Prusa MINIIS 0.4 nozzle",
"Prusa MINIIS 0.6 nozzle",
"Prusa MINIIS 0.8 nozzle"
],
"Generic TPU @Anycubic": [
"Anycubic Kobra 3 0.4 nozzle"
],
"Generic TPU @Creality": [
"Creality CR-10 SE 0.2 nozzle",
"Creality CR-10 SE 0.4 nozzle",
"Creality CR-10 SE 0.6 nozzle",
"Creality CR-10 SE 0.8 nozzle"
],
"Generic TPU @Creality Ender-3V3-all": [
"Creality Ender-3 V3 0.4 nozzle",
"Creality Ender-3 V3 0.6 nozzle",
"Creality Ender-3 V3 Plus 0.4 nozzle",
"Creality Ender-3 V3 Plus 0.6 nozzle",
"Creality Ender-3 V3 SE 0.2 nozzle",
"Creality Ender-3 V3 SE 0.4 nozzle",
"Creality Ender-3 V3 SE 0.6 nozzle",
"Creality Ender-3 V3 SE 0.8 nozzle"
],
"Generic TPU @Creality K1-all": [
"Creality K1 SE 0.4 nozzle",
"Creality K1C 0.4 nozzle"
],
"Generic TPU @Creality K2-all": [
"Creality K2 0.4 nozzle",
"Creality K2 Plus 0.4 nozzle",
"Creality K2 Pro 0.4 nozzle"
],
"HATCHBOX PETG @0.2 nozzle": [
"Qidi Q1 Pro 0.2 nozzle"
],
"QIDI PETG Basic @0.2 nozzle": [
"Qidi Q1 Pro 0.2 nozzle"
],
"QIDI PETG Rapido @0.2 nozzle": [
"Qidi Q1 Pro 0.2 nozzle"
],
"QIDI PETG Translucent @0.2 nozzle": [
"Qidi Q1 Pro 0.2 nozzle"
],
"re3D PC @0.4 nozzle": [
"re3D Gigabot 4 0.8 nozzle",
"re3D Gigabot 4 XLT 0.8 nozzle",
"re3D Terabot 4 0.8 nozzle"
],
"re3D PETG @0.4 nozzle": [
"re3D Gigabot 4 0.8 nozzle",
"re3D Gigabot 4 XLT 0.8 nozzle",
"re3D Terabot 4 0.8 nozzle"
],
"re3D PLA @0.4 nozzle": [
"re3D Gigabot 4 0.8 nozzle",
"re3D Gigabot 4 XLT 0.8 nozzle",
"re3D Terabot 4 0.8 nozzle"
],
"re3D rPETG @0.8 nozzle": [
"re3D GigabotX 2 1.75 nozzle",
"re3D GigabotX 2 XLT 1.75 nozzle",
"re3D TerabotX 2 1.75 nozzle"
],
"re3D rPP @0.8 nozzle": [
"re3D GigabotX 2 1.75 nozzle",
"re3D GigabotX 2 XLT 1.75 nozzle",
"re3D TerabotX 2 1.75 nozzle"
]
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Qidi",
"version": "02.04.00.16",
"version": "02.04.00.17",
"force_update": "0",
"description": "Qidi configurations",
"machine_model_list": [
@@ -83,7 +83,7 @@
"extruder_clearance_height_to_lid": "120",
"layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nLOG_Z\nG92 E0\n",
"machine_end_gcode": "M141 S0\nM104 S0\nM140 S0\nG1 E-3 F1800\nG0 Z{min(max_print_height, max_layer_z + 3)} F600\nG0 X0 Y0 F12000\n{if max_layer_z < max_print_height / 2}G1 Z{max_print_height / 2 + 10} F600{else}G1 Z{min(max_print_height, max_layer_z + 3)}{endif}",
"machine_start_gcode": "PRINT_START BED=[hot_plate_temp_initial_layer] HOTEND=[nozzle_temperature_initial_layer] CHAMBER=[chamber_temperature]\nM83\nM140 S[hot_plate_temp_initial_layer]\nM104 S[nozzle_temperature_initial_layer]\nG4 P3000\nG0 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0)} Z5 F6000\nG0 Z[initial_layer_print_height] F600\nG1 E3 F1800\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 80))} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0)} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 85} E{83 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 3} E{82 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 3} Z0\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 6}\nG1 Z1 F600\n",
"machine_start_gcode": "PRINT_START BED=[bed_temperature_initial_layer_single] HOTEND=[nozzle_temperature_initial_layer] CHAMBER=[chamber_temperature]\nM83\nM140 S[bed_temperature_initial_layer_single]\nM104 S[nozzle_temperature_initial_layer]\nG4 P3000\nG0 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0)} Z5 F6000\nG0 Z[initial_layer_print_height] F600\nG1 E3 F1800\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 80))} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0)} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 85} E{83 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 3} E{82 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 3} Z0\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 6}\nG1 Z1 F600\n",
"thumbnails_format": "PNG",
"default_filament_profile": [
"Generic PLA @Qidi Q1 Pro 0.4 nozzle"
@@ -100,7 +100,7 @@
"default_filament_profile": [
"Generic PLA @Qidi"
],
"machine_start_gcode": "G28\nM140 S[hot_plate_temp_initial_layer]\nM190 S[hot_plate_temp_initial_layer]\nM109 S[nozzle_temperature_initial_layer]\nG92 E-19\nG0 Y5 Z0.3 F3600\nG1 X5 E0 F2400\n",
"machine_start_gcode": "G28\nM140 S[bed_temperature_initial_layer_single]\nM190 S[bed_temperature_initial_layer_single]\nM109 S[nozzle_temperature_initial_layer]\nG92 E-19\nG0 Y5 Z0.3 F3600\nG1 X5 E0 F2400\n",
"machine_end_gcode": "M104 S0\nM140 S0\n;Retract the filament\nG92 E0\nG1 E-3 F300\nG28\nM84\n",
"scan_first_layer": "0"
}
@@ -24,7 +24,7 @@
"210x210/COLPIC",
"110x110/PNG"
],
"machine_start_gcode": "PRINT_START\nG28\nM141 S0\nG0 Z50 F600\nM190 S[hot_plate_temp_initial_layer]\nG28 Z\nG29; mesh bed leveling ,comment this code to close it\nG0 X0 Y0 Z50 F6000\nM141 S{overall_chamber_temperature}\nM109 S[nozzle_temperature_initial_layer]\nM106 P3 S255\nM83\nG4 P3000\nG0 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0)} Z5 F6000\nG0 Z[initial_layer_print_height] F600\nG1 E3 F1800\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 80))} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 85} E{83 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 3} E{82 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0) + 12} E{-10 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 E{10 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\n",
"machine_start_gcode": "PRINT_START\nG28\nM141 S0\nG0 Z50 F600\nM190 S[bed_temperature_initial_layer_single]\nG28 Z\nG29; mesh bed leveling ,comment this code to close it\nG0 X0 Y0 Z50 F6000\nM141 S{overall_chamber_temperature}\nM109 S[nozzle_temperature_initial_layer]\nM106 P3 S255\nM83\nG4 P3000\nG0 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0)} Z5 F6000\nG0 Z[initial_layer_print_height] F600\nG1 E3 F1800\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 80))} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 85} E{83 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 3} E{82 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0) + 12} E{-10 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 E{10 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\n",
"machine_end_gcode": "M141 S0\nM104 S0\nM140 S0\nG1 E-3 F1800\nG0 Z{min(max_print_height, max_layer_z + 3)} F600\nG0 X0 Y0 F12000\n{if max_layer_z < max_print_height / 2}G1 Z{max_print_height / 2 + 10} F600{else}G1 Z{min(max_print_height, max_layer_z + 3)}{endif}",
"scan_first_layer": "0"
}
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env sh
# WebKit's NVIDIA DMABUF workaround (gnome-build-meta#754) is applied by the
# binary itself, gated on the runtime WebKitGTK version (src/OrcaSlicer.cpp).
# Work-around https://gitlab.gnome.org/GNOME/gnome-build-meta/-/issues/754
grep -q org.freedesktop.Platform.GL.nvidia /.flatpak-info && export WEBKIT_DISABLE_DMABUF_RENDERER=1
# Work-around https://github.com/bambulab/BambuStudio/issues/3440
# Use LC_NUMERIC instead of LC_ALL to prevent decimal separator issues
+154
View File
@@ -11,6 +11,7 @@ commands:
normalize rewrite profile files into their canonical shape
trim delete profile files no <vendor>.json list references
update-index regenerate the *_list sections of <vendor>.json
legacy-compat write the printers user presets can be migrated back to
options shared by several commands:
--vendor VENDOR act on one vendor bundle only; repeatable, empty means all
@@ -24,6 +25,9 @@ After adding, renaming or deleting profile files, run:
normalize -> update-index -> generate-id -> check
normalize supplies missing types; update-index registers presets before id
generation.
After renaming filament presets or narrowing their compatible_printers, also
rerun legacy-compat --base <2.4.2 profiles> so user presets made against 2.4.2
can be migrated back to their printers.
Use trim only for deliberate cleanup, previewed with --dry-run: it judges against
the current index and can delete newly added, unindexed presets.
@@ -3072,6 +3076,131 @@ def fix_variant_arrays(profiles_dir=PROFILES_DIR, vendors=None, dry_run=False, s
return len(changed), errors
# ---------------------------------------------------------------------------
# legacy-compat
# ---------------------------------------------------------------------------
LEGACY_COMPAT_PATH = os.path.normpath(
os.path.join(SCRIPTS_DIR, "..", "resources", "info", "legacy_compatible_printers.json"))
def _renamed_from(data):
"""A preset's renamed_from names. Profiles write one ';'-separated string, as the
loader's unescape_strings_cstyle reads it; none of them quote or escape a name."""
value = data.get("renamed_from") or []
return [n for v in _as_list(value) for n in v.split(";") if n]
def _flatten_presets(profiles_dir):
"""Every bundle's composed filament and printer presets, for legacy-compat.
Returns (filaments, printers): filaments maps (vendor, name) to a dict with the
resolved compatible_printers, filament_id, extruder variants, renamed_from and
whether it is instantiated; printers maps each instantiated printer name to a
dict with its renamed_from and extruder variants. instantiation is read from the
preset's own file, as the loader requires every file to state it.
"""
def instantiated(data):
return str(data.get("instantiation", "")).lower() == "true"
ofl = load_vendor_configs(profiles_dir, OFL)["filament"]
filaments, printers = {}, {}
for vendor in list_vendor_names(profiles_dir):
configs = load_vendor_configs(profiles_dir, vendor)
cache = {}
for name, (_rel, data) in configs["filament"].items():
config = compose_config(name, configs["filament"], ofl, cache, vendor == OFL)
filaments[(vendor, name)] = {
"compatible_printers": set(_as_list(config.get("compatible_printers", ([],))[0])),
"filament_id": config.get("filament_id", (None,))[0],
"variants": set(_as_list(config.get("filament_extruder_variant", ([],))[0])),
"instantiation": instantiated(data),
"renamed_from": _renamed_from(data),
}
cache = {}
for name, (_rel, data) in configs["machine"].items():
if instantiated(data):
config = compose_config(name, configs["machine"], {}, cache)
printers[name] = {
"renamed_from": _renamed_from(data),
"variants": set(_as_list(config.get("printer_extruder_variant", ([],))[0])),
}
return filaments, printers
def legacy_compatible_printers(base_dir, profiles_dir=PROFILES_DIR):
"""Printers each current system filament preset covered in the base release
but no longer lists, kept only where a current preset with the same
filament_id still covers that printer.
User presets store only their diff, so they inherit their parent's
compatible_printers; when a profile change narrows that list, a user preset
made against the base release disappears from printers it was used on. The
app offers to write these printers into such user presets. Requiring a same-id
successor leaves out printers a filament was deliberately taken off, and
requiring a shared extruder variant keeps a printer from picking up another
variant's values.
Keyed by the current preset name: the app resolves a renamed parent
before the map is consulted. Printer names are translated through the
printers' renamed_from. Returns {preset name: sorted printer names}.
"""
old_filaments, _ = _flatten_presets(base_dir)
new_filaments, new_printers = _flatten_presets(profiles_dir)
renamed_printer = {old: new for new, rec in new_printers.items() for old in rec["renamed_from"]}
old_by_name = {}
for (vendor, name), rec in old_filaments.items():
old_by_name.setdefault(name, []).append((vendor, rec))
covering = {} # filament_id -> printers some current instantiated preset covers
for rec in new_filaments.values():
if rec["instantiation"] and rec["filament_id"]:
covering.setdefault(rec["filament_id"], set()).update(rec["compatible_printers"])
def shares_variant(filament, printer):
variants = new_printers[printer]["variants"]
return not filament["variants"] or not variants or bool(filament["variants"] & variants)
result = {}
for (vendor, name), rec in sorted(new_filaments.items()):
# An empty list means every printer, so nothing can have been lost.
if not rec["instantiation"] or not rec["compatible_printers"]:
continue
old_printers = set()
for old_name in [name] + rec["renamed_from"]:
candidates = old_by_name.get(old_name, [])
same_vendor = [r for v, r in candidates if v == vendor]
for old in same_vendor or [r for _, r in candidates]:
old_printers |= old["compatible_printers"]
old_printers = {renamed_printer.get(p, p) for p in old_printers}
lost = old_printers - rec["compatible_printers"]
lost &= covering.get(rec["filament_id"], set()) & set(new_printers)
lost = {p for p in lost if shares_variant(rec, p)}
if lost:
result[name] = sorted(lost)
return result
def run_legacy_compat(base_dir, profiles_dir, output, check):
presets = legacy_compatible_printers(base_dir, profiles_dir)
if check:
try:
with open(output, encoding="utf-8") as f:
current = json.load(f).get("presets")
except (OSError, ValueError, AttributeError):
current = None
if current != presets:
print_error(f"{output} is out of date; rerun legacy-compat --base {base_dir}")
return 1
print_success(f"{output} is up to date")
return 0
with open(output, "w", encoding="utf-8", newline="\n") as f:
f.write(json.dumps({"presets": presets}, indent=4, sort_keys=True, ensure_ascii=False) + "\n")
print_info(f"wrote {len(presets)} presets / {sum(map(len, presets.values()))} printers to {output}")
return 0
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
@@ -3093,11 +3222,17 @@ examples:
preview resizing the variant arrays check rejects in that bundle
orca_profile_tool.py check --strict --vendor BBL
also hold every preset's inherited variant arrays to its own width
orca_profile_tool.py legacy-compat --base /tmp/v2.4.2/resources/profiles
rewrite resources/info/legacy_compatible_printers.json against 2.4.2's
profiles (git archive v2.4.2 resources/profiles | tar -x -C /tmp/v2.4.2)
after adding, renaming or deleting profile files, run in this order:
normalize -> update-index -> generate-id -> check
normalize supplies missing types; update-index registers presets before id
generation.
After renaming filament presets or narrowing their compatible_printers, also
rerun legacy-compat --base <2.4.2 profiles> so user presets made against 2.4.2
can be migrated back to their printers.
Use trim only for deliberate cleanup, previewed with --dry-run: it judges against
the current index and can delete newly added, unindexed presets.
"""
@@ -3227,6 +3362,22 @@ def build_parser():
"Use trim only for deliberate unindexed-file cleanup, previewed with\n"
"--dry-run; it can also delete newly authored presets.")
legacy_cmd = add(
"legacy-compat", [profiles_opt],
"write the printers user presets keep from an older release",
"Compare the tree with an older release's profiles and write, per current\n"
"filament preset, the printers it covered then and no longer lists, where a\n"
"preset with the same filament_id still covers them. The loader adds these\n"
"to user presets that inherit the preset, so a narrowed compatible_printers\n"
"does not hide them on printers they were made for.")
legacy_cmd.add_argument("--base", required=True, metavar="DIR",
help="the older release's resources/profiles directory")
legacy_cmd.add_argument("--output", default=LEGACY_COMPAT_PATH, metavar="FILE",
help="map to write (default: resources/info/"
"legacy_compatible_printers.json)")
legacy_cmd.add_argument("--check", action="store_true",
help="fail if the map differs from what --base implies; write nothing")
return parser
@@ -3260,6 +3411,9 @@ def main(argv=None):
return run_generate_id(profiles_dir, vendors, args.filament_id, args.setting_id,
args.dry_run)
if args.command == "legacy-compat":
return run_legacy_compat(args.base, profiles_dir, args.output, args.check)
if args.command == "fix-variant":
_changed, errors = fix_variant_arrays(profiles_dir, vendors, dry_run=args.dry_run,
strict=args.strict)
+91
View File
@@ -1258,6 +1258,97 @@ class TestVariantNames(TreeCase):
self.assertIn('holds the legacy variant "Direct Drive Normal"', out)
# ---------------------------------------------------------------------------
# legacy-compat
# ---------------------------------------------------------------------------
class TestLegacyCompat(unittest.TestCase):
"""A base release and the current tree, each with one vendor."""
def setUp(self):
self.base, self.cur = Tree(), Tree()
self.addCleanup(self.base.cleanup)
self.addCleanup(self.cur.cleanup)
@staticmethod
def printer(tree, name, **extra):
tree.write("V", f"machine/{name}.json",
{"type": "machine", "name": name, "instantiation": "true", **extra})
tree.index("V", "machine", name, f"machine/{name}.json")
@staticmethod
def filament(tree, name, printers, filament_id="OFpetg01", **extra):
tree.write("V", f"filament/{name}.json",
{"type": "filament", "name": name, "instantiation": "true",
"filament_id": filament_id, "compatible_printers": printers, **extra})
tree.index("V", "filament", name, f"filament/{name}.json")
def generate(self):
return apt.legacy_compatible_printers(self.base.profiles, self.cur.profiles)
def test_renamed_and_narrowed_preset_keeps_printers_a_same_id_sibling_covers(self):
for tree in (self.base, self.cur):
for p in ("A 0.4", "A 0.6", "B 0.4"):
self.printer(tree, p)
self.filament(self.base, "PETG @A-all", ["A 0.4", "A 0.6", "B 0.4"])
self.filament(self.cur, "PETG @V A-all", ["A 0.6"], renamed_from="PETG @A-all;PETG A-all")
self.filament(self.cur, "PETG @A 0.4", ["A 0.4"])
self.assertEqual(self.generate(), {"PETG @V A-all": ["A 0.4"]})
def test_printer_without_a_successor_is_left_out(self):
for tree in (self.base, self.cur):
for p in ("A 0.4", "A 0.6"):
self.printer(tree, p)
self.filament(self.base, "PETG @A", ["A 0.4", "A 0.6"])
self.filament(self.cur, "PETG @A", ["A 0.6"])
self.filament(self.cur, "Other @A", ["A 0.4"], filament_id="OFother1")
self.assertEqual(self.generate(), {})
def test_printer_names_follow_printer_renames(self):
self.printer(self.base, "Old 0.4")
self.printer(self.base, "A 0.6")
self.printer(self.cur, "New 0.4", renamed_from="Old 0.4")
self.printer(self.cur, "A 0.6")
self.filament(self.base, "PETG @A", ["Old 0.4", "A 0.6"])
self.filament(self.cur, "PETG @A", ["A 0.6"])
self.filament(self.cur, "PETG @New", ["New 0.4"])
self.assertEqual(self.generate(), {"PETG @A": ["New 0.4"]})
def test_printer_without_a_shared_extruder_variant_is_left_out(self):
for tree in (self.base, self.cur):
self.printer(tree, "Direct 0.4", printer_extruder_variant=["Direct Drive Standard"])
self.printer(tree, "Bowden 0.4", printer_extruder_variant=["Bowden Standard"])
self.filament(self.base, "PETG @all", ["Direct 0.4", "Bowden 0.4"])
self.filament(self.cur, "PETG @all", ["Direct 0.4"],
filament_extruder_variant=["Direct Drive Standard"])
self.filament(self.cur, "PETG @Bowden", ["Bowden 0.4"])
self.assertEqual(self.generate(), {})
def test_preset_now_listing_no_printer_is_skipped(self):
for tree in (self.base, self.cur):
self.printer(tree, "A 0.4")
self.filament(self.base, "PETG @A", ["A 0.4"])
self.filament(self.cur, "PETG @A", [])
self.assertEqual(self.generate(), {})
def test_check_passes_on_a_current_map_and_fails_on_a_stale_one(self):
for tree in (self.base, self.cur):
for p in ("A 0.4", "A 0.6"):
self.printer(tree, p)
self.filament(self.base, "PETG @A", ["A 0.4", "A 0.6"])
self.filament(self.cur, "PETG @A", ["A 0.6"])
self.filament(self.cur, "PETG @A 0.4", ["A 0.4"])
output = os.path.join(self.cur.dir, "map.json")
argv = ["legacy-compat", "--base", self.base.profiles, "--profiles", self.cur.profiles,
"--output", output]
with contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(apt.main(argv), 0)
self.assertEqual(apt.main(argv + ["--check"]), 0)
with open(output, "w", encoding="utf-8") as f:
json.dump({"presets": {}}, f)
self.assertEqual(apt.main(argv + ["--check"]), 1)
class TestDispatch(TreeCase):
def test_each_command_reaches_its_own_writer(self):
self.t.write("V", "filament/A.json", {"type": "filament", "name": "A"})
+8 -18
View File
@@ -161,7 +161,6 @@ using json = nlohmann::json;
#include <X11/Xlib.h>
#endif
#include <unistd.h>
#include "slic3r/GUI/Widgets/WebView.hpp"
#endif
#ifdef SLIC3R_GUI
@@ -1154,9 +1153,8 @@ int CLI::run(int argc, char **argv)
// opt-in path. The ~2020-era WebKit2GTK XWayland compositor bug it
// worked around appears fixed in WebKit2GTK >= 2.42; leaving it
// unset preserves WebKit hardware acceleration on Device / Setup
// Wizard / login / store. The default path applies it only on
// XWayland sessions and only when the linked WebKitGTK predates the
// fix (< 2.42), as a conservative fallback for older WebKit.
// Wizard / login / store. The default path still applies it on
// XWayland sessions as a conservative fallback for older WebKit.
// ------------------------------------------------------------------
{
const char* gdk_backend = ::getenv("GDK_BACKEND");
@@ -1164,14 +1162,6 @@ int CLI::run(int argc, char **argv)
// honours the first backend in the comma-separated list).
const bool x11_opt_in = gdk_backend && boost::starts_with(gdk_backend, "x11");
// NVIDIA's proprietary driver cannot back WebKitGTK's DMABUF renderer.
// Force the shared-memory transport, which keeps compositing and threaded
// scrolling; the second variable clears Debian/Ubuntu/openSUSE's isNVIDIA().
if (::access("/proc/driver/nvidia/version", F_OK) == 0 && WebView::SupportsForceSharedMemory()) {
::setenv("WEBKIT_DMABUF_RENDERER_FORCE_SHM", "1", /* replace */ false);
::setenv("WEBKIT_FORCE_DMABUF_RENDERER", "1", /* replace */ false);
}
if (x11_opt_in) {
// ===== X11 / XWayland (opted in via GDK_BACKEND=x11) =====
BOOST_LOG_TRIVIAL(info) << "GDK_BACKEND=x11 detected; applying v2.3.2 X11/XWayland workarounds.";
@@ -1215,15 +1205,15 @@ int CLI::run(int argc, char **argv)
}
#endif
// WebKit2GTK accelerated compositing could fail under XWayland
// on WebKit releases before 2.42. Disable it only then, and only
// when both DISPLAY and WAYLAND_DISPLAY are set (an XWayland
// session). Disabling it also turns off threaded scrolling, so on
// newer WebKit it only costs performance.
// WebKit2GTK compositing can fail under XWayland on older
// WebKit releases. Disable it only when both DISPLAY and
// WAYLAND_DISPLAY are set (i.e. an XWayland session is in
// use). On pure X11 or native Wayland, compositing is left
// enabled so WebKit retains HW acceleration.
{
const char* display_env_wk = ::getenv("DISPLAY");
const char* wayland_env_wk = ::getenv("WAYLAND_DISPLAY");
if (WebView::NeedsCompositingWorkaround() && display_env_wk && *display_env_wk && wayland_env_wk && *wayland_env_wk) {
if (display_env_wk && *display_env_wk && wayland_env_wk && *wayland_env_wk) {
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false);
}
}
@@ -429,8 +429,7 @@ if [ "\$XDG_SESSION_TYPE" = "wayland" ] && [ "\$ZINK_DISABLE_OVERRIDE" != "1" ];
export __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json
export MESA_LOADER_DRIVER_OVERRIDE=zink
export GALLIUM_DRIVER=zink
# WebKit's NVIDIA DMABUF workaround is applied by the binary itself,
# gated on the runtime WebKitGTK version (src/OrcaSlicer.cpp).
export WEBKIT_DISABLE_DMABUF_RENDERER=1
fi
fi
fi
+2 -2
View File
@@ -1729,8 +1729,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//plate->pattern_file = it->second->pattern_file;
plate->no_light_thumbnail_file = it->second->no_light_thumbnail_file;
plate->top_file = it->second->top_file;
plate->pick_file = it->second->pick_file.empty();
plate->pattern_bbox_file = it->second->pattern_bbox_file.empty();
plate->pick_file = it->second->pick_file;
plate->pattern_bbox_file = it->second->pattern_bbox_file;
plate->config = it->second->config;
if (!plate->thumbnail_file.empty())
+1 -1
View File
@@ -3295,7 +3295,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
m_is_role_based_fan_on.fill(false);
m_role_based_fan_marker_layer.fill(-1);
m_fan_mover.release();
m_fan_mover.reset();
m_ordering_cache.clear();
// Belt printer: initialize belt-specific writer via virtual hook.
+90
View File
@@ -81,6 +81,7 @@
#include "LifecycleEvents.hpp"
#include "Utils.hpp"
#include "InstanceLock.hpp"
#include "nlohmann/json.hpp"
#include <sstream>
#include "PlaceholderParser.hpp"
@@ -2728,6 +2729,95 @@ void PresetCollection::update_after_user_presets_loaded()
return;
}
const Preset *PresetCollection::system_parent_with_inherited_printers(const Preset &preset) const
{
if (!preset.is_user() || preset.inherits().empty())
return nullptr;
const Preset *parent = get_preset_parent(preset);
if (parent == nullptr || !parent->is_system)
return nullptr;
// A user preset that sets its own compatible_printers keeps exactly those.
const auto *own = preset.config.option<ConfigOptionStrings>("compatible_printers");
const auto *inherited = parent->config.option<ConfigOptionStrings>("compatible_printers");
return own != nullptr && inherited != nullptr && own->values == inherited->values ? parent : nullptr;
}
std::vector<LegacyPrinterMigration> PresetCollection::legacy_printer_migrations(const std::map<std::string, std::vector<std::string>> &printers_by_parent) const
{
std::vector<LegacyPrinterMigration> migrations;
for (const Preset &preset : m_presets) {
const Preset *parent = system_parent_with_inherited_printers(preset);
if (parent == nullptr)
continue;
// An empty list already covers every printer.
const std::vector<std::string> &own = preset.config.option<ConfigOptionStrings>("compatible_printers")->values;
if (own.empty())
continue;
// The map names the parent as it was when the map was written; a later rename keeps that name in renamed_from.
auto it = printers_by_parent.find(parent->name);
for (auto old_name = parent->renamed_from.begin(); it == printers_by_parent.end() && old_name != parent->renamed_from.end(); ++old_name)
it = printers_by_parent.find(*old_name);
if (it == printers_by_parent.end())
continue;
LegacyPrinterMigration migration{preset.name, parent->name, {}};
for (const std::string &printer : it->second)
if (std::find(own.begin(), own.end(), printer) == own.end())
migration.printers.push_back(printer);
if (!migration.printers.empty())
migrations.push_back(std::move(migration));
}
return migrations;
}
bool PresetCollection::apply_legacy_printer_migration(const LegacyPrinterMigration &migration)
{
lock();
auto it = this->find_preset_internal(migration.preset);
if (it == m_presets.end() || it->name != migration.preset || system_parent_with_inherited_printers(*it) == nullptr) {
unlock();
return false;
}
Preset &preset = *it;
std::vector<std::string> printers = preset.config.option<ConfigOptionStrings>("compatible_printers")->values;
for (const std::string &printer : migration.printers)
if (std::find(printers.begin(), printers.end(), printer) == printers.end())
printers.push_back(printer);
const bool selected = m_idx_selected == static_cast<size_t>(it - m_presets.begin());
unlock();
// Only compatible_printers changes in the file. Saving the whole preset would also rewrite an inherits that
// names a renamed parent by its old name, the only name older releases know it by.
{
InstanceLock instance_lock(user_presets_lock_path());
try {
boost::nowide::ifstream in(preset.file);
json j = json::parse(in);
j["compatible_printers"] = printers;
if (const std::error_code ec = write_file_atomically(preset.file, j.dump(1, '\t'))) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to save " << preset.file << ": " << ec.message();
return false;
}
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": can not migrate " << preset.file << ": " << err.what();
return false;
}
}
lock();
preset.config.option<ConfigOptionStrings>("compatible_printers")->values = printers;
if (selected)
m_edited_preset.config.option<ConfigOptionStrings>("compatible_printers", true)->values = printers;
unlock();
// Push the change to the cloud copy, as an edit in the settings tab would.
if (!preset.setting_id.empty()) {
preset.sync_info = "update";
preset.save_info();
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": user preset " << preset.name << " gets " << migration.printers.size()
<< " printer(s) its parent listed in an older release";
return true;
}
//BBS: validate_preset
bool PresetCollection::validate_preset(const std::string &preset_name, std::string &inherit_name)
{
+20
View File
@@ -551,6 +551,14 @@ struct PresetConfigSubstitutions {
// PrusaSlicer and reading the user Print / Filament / Printer profiles.
using PresetsConfigSubstitutions = std::vector<PresetConfigSubstitutions>;
// A user filament preset that no longer shows on printers its system parent listed in an older release,
// and those printers. Migrating it writes them into the preset's own compatible_printers.
struct LegacyPrinterMigration {
std::string preset;
std::string parent;
std::vector<std::string> printers;
};
// Collections of presets of the same type (one of the Print, Filament or Printer type).
class PresetCollection
{
@@ -601,6 +609,14 @@ public:
void save_user_presets(const std::string& dir_path, const std::string& type, std::map<std::string, std::string>& need_to_delete_list);
bool load_user_preset(std::string name, std::map<std::string, std::string> preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, const PresetOrigin &load_origin = PresetOrigin(PresetOrigin::Kind::User));
void update_after_user_presets_loaded();
// User presets store only their diff, so a user preset inherits its system parent's compatible_printers
// and disappears from a printer once a profile update narrows that list. Lists the user presets that
// inherit the list and the printers their parent covered in an older release (printers_by_parent, keyed
// by the parent's name then) but no longer lists.
std::vector<LegacyPrinterMigration> legacy_printer_migrations(const std::map<std::string, std::vector<std::string>> &printers_by_parent) const;
// Adds the migration's printers to the preset's own compatible_printers and saves the preset to disk.
// False if the preset no longer inherits its parent's list or could not be saved.
bool apply_legacy_printer_migration(const LegacyPrinterMigration &migration);
//BBS: get user presets
int get_user_presets(PresetBundle *preset_bundle, std::vector<Preset> &result_presets);
void set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time, const std::string& user_id);
@@ -893,6 +909,10 @@ public:
size_t num_default_presets() { return m_num_default_presets; }
protected:
// The system parent of a user preset whose compatible_printers is the parent's, or nullptr: the presets
// legacy_printer_migrations() considers.
const Preset* system_parent_with_inherited_printers(const Preset &preset) const;
PresetCollection() = default;
// Deleted by the std::recursive_mutex member. PresetBundle copies by assignment.
PresetCollection(const PresetCollection &other) = delete;
+81
View File
@@ -2,6 +2,7 @@
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/directory.hpp>
#include <boost/filesystem/file_status.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/optional/optional.hpp>
#include <boost/algorithm/string/split.hpp>
@@ -24,6 +25,7 @@
#include <memory>
#include <initializer_list>
#include <map>
#include <mutex>
#include <iterator>
#include <functional>
#include <limits>
@@ -38,6 +40,8 @@
#include "ParallelResolve.hpp"
#include "Preset.hpp"
#include "PresetCacheFormat.hpp"
#include "Time.hpp"
#include "Zipper.hpp"
#include "PrintConfig.hpp"
#include "PublishSettings.hpp"
#include "FilamentMixer.hpp"
@@ -96,6 +100,34 @@ using json = nlohmann::json;
namespace Slic3r {
// Per current system filament preset, the printers it listed in an older release and no longer does
// (resources/info/legacy_compatible_printers.json, written by orca_profile_tool.py legacy-compat).
// The file ships with the build, so it is read once it opens; until then (e.g. presets loaded before
// resources_dir() is set) every call tries again and there are no legacy printers.
static std::map<std::string, std::vector<std::string>> legacy_compatible_printers()
{
static std::mutex mutex;
static std::map<std::string, std::vector<std::string>> printers_by_parent;
static bool read = false;
std::lock_guard<std::mutex> guard(mutex);
if (!read) {
const fs::path path = fs::path(resources_dir()) / "info" / "legacy_compatible_printers.json";
boost::nowide::ifstream in(path.string());
if (!in) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": " << path.string() << " not found";
return {};
}
read = true;
try {
printers_by_parent = json::parse(in).at("presets").get<std::map<std::string, std::vector<std::string>>>();
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": can not read " << path.string() << ": " << err.what();
}
}
return printers_by_parent;
}
// Project-level options imported from a loaded 3MF into project_config. s_project_options_published
// below is the reduced subset that crosses over in "published" 3MF mode; keep both in sync.
// s_project_options_published additionally carries wipe_tower_rotation_angle, which normal
@@ -1045,6 +1077,55 @@ bool PresetBundle::backup_user_folder() const
}
}
bool PresetBundle::backup_user_folder_to_zip(const std::string &zip_path) const
{
const fs::path user_dir = fs::path(data_dir()) / PRESET_USER_DIR;
BOOST_LOG_TRIVIAL(info) << "Backing up user folder to: " << zip_path;
try {
Zipper zipper(zip_path);
// Directories get their own entries, which extract_archive_confined() needs to recreate them.
zipper.add_entry(std::string(PRESET_USER_DIR) + "/", nullptr, 0);
for (const fs::directory_entry &entry : fs::recursive_directory_iterator(user_dir)) {
const std::string name = (fs::path(PRESET_USER_DIR) / entry.path().lexically_relative(user_dir)).generic_string();
if (fs::is_directory(entry.status())) {
zipper.add_entry(name + "/", nullptr, 0);
continue;
}
if (!fs::is_regular_file(entry.status()))
continue;
boost::nowide::ifstream in(entry.path().string(), std::ios::binary);
const std::string data((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (!in.good() && !in.eof())
throw Slic3r::RuntimeError("can not read " + entry.path().string());
zipper.add_entry(name, data.data(), data.size());
}
zipper.finalize();
return true;
} catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << "Exception during user folder backup: " << ex.what();
boost::system::error_code ec;
fs::remove(zip_path, ec);
return false;
}
}
std::vector<LegacyPrinterMigration> PresetBundle::legacy_printer_migrations() const
{
return filaments.legacy_printer_migrations(legacy_compatible_printers());
}
std::string PresetBundle::migrate_legacy_printers(const std::vector<LegacyPrinterMigration> &migrations, std::vector<std::string> &failed)
{
const std::string zip_path = data_dir() + "/filament_preset_migration_" +
Utils::time2str(Utils::TimeZone::utc, Utils::TimeFormat::iso8601Z) + "_backup.zip";
if (!backup_user_folder_to_zip(zip_path))
return {};
for (const LegacyPrinterMigration &migration : migrations)
if (!filaments.apply_legacy_printer_migration(migration))
failed.push_back(migration.preset);
return zip_path;
}
std::optional<FilamentBaseInfo> PresetBundle::get_filament_by_filament_id(const std::string& filament_id, const std::string& printer_name) const
{
if (filament_id.empty())
+10
View File
@@ -376,6 +376,16 @@ public:
bool use_bbl_device_tab();
bool backup_user_folder() const;
// Writes the user preset folder into a zip archive at zip_path, its entries under "user/".
bool backup_user_folder_to_zip(const std::string &zip_path) const;
// User filament presets that no longer show on printers their parent listed in an older release
// (resources/info/legacy_compatible_printers.json).
std::vector<LegacyPrinterMigration> legacy_printer_migrations() const;
// Backs up the user preset folder into a zip in data_dir(), then writes each migration's printers into its
// preset file. Returns the zip's path, or an empty string when the backup failed and nothing was migrated.
// The presets that could not be migrated are added to failed.
std::string migrate_legacy_printers(const std::vector<LegacyPrinterMigration> &migrations, std::vector<std::string> &failed);
//BBS: project embedded preset logic
PresetsConfigSubstitutions load_project_embedded_presets(std::vector<Preset*> project_presets, ForwardCompatibilitySubstitutionRule substitution_rule);
+2
View File
@@ -362,6 +362,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Lazy.hpp
GUI/LazyPage.cpp
GUI/LazyPage.hpp
GUI/LegacyPrinterMigrationDialog.cpp
GUI/LegacyPrinterMigrationDialog.hpp
GUI/LibVGCode/LibVGCodeWrapper.hpp
GUI/LibVGCode/LibVGCodeWrapper.cpp
GUI/LinuxDisplayBackend.cpp
+173 -49
View File
@@ -16,6 +16,7 @@
#include <wx/checklst.h>
#include "slic3r/GUI/GUI_Utils.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include "slic3r/GUI/Widgets/DropDown.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include <utility>
#include "slic3r/GUI/wxExtensions.hpp"
@@ -172,7 +173,26 @@ void AMSMaterialsSetting::create()
Bind(wxEVT_PAINT, &AMSMaterialsSetting::paintEvent, this);
Bind(EVT_SELECTED_COLOR, &AMSMaterialsSetting::on_picker_color, this);
m_comboBox_filament->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_filament), NULL, this);
m_comboBox_filament->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_filament), NULL, this);
m_comboBox_filament->Bind(EVT_DROPDOWN_ITEM_ACTION, &AMSMaterialsSetting::on_toggle_favorite_filament, this);
// Favorites toggled in the open list are regrouped just before it opens again, so rows never move
// under the cursor. These handlers run ahead of ComboBox's own, which then measure and show the
// rebuilt rows; wxEVT_COMBOBOX_DROPDOWN would be too late, as ComboBox sends it after showing the list.
auto regroup_favorites = [this]() {
if (!m_favorites_regroup || m_comboBox_filament->is_drop_down())
return;
const FilamentInfos* info = selected_filament_info();
const std::string filament_id = info ? info->filament_id : std::string();
rebuild_filament_dropdown_items(filament_id);
};
m_comboBox_filament->Bind(wxEVT_LEFT_DOWN, [regroup_favorites](wxMouseEvent& e) { regroup_favorites(); e.Skip(); });
m_comboBox_filament->Bind(wxEVT_LEFT_DCLICK, [regroup_favorites](wxMouseEvent& e) { regroup_favorites(); e.Skip(); });
m_comboBox_filament->Bind(wxEVT_KEY_DOWN, [regroup_favorites](wxKeyEvent& e) {
if (e.GetKeyCode() == WXK_RETURN || e.GetKeyCode() == WXK_SPACE) // the keys that open the list
regroup_favorites();
e.Skip();
});
m_comboBox_cali_result->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_cali_result), NULL, this);
}
@@ -191,11 +211,9 @@ void AMSMaterialsSetting::create_panel_normal(wxWindow* parent)
m_sizer_filament->Add(0, 0, 0, wxEXPAND, 0);
#ifdef __APPLE__
m_comboBox_filament = new wxComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, AMS_MATERIALS_SETTING_COMBOX_WIDTH, 0, nullptr, wxCB_READONLY);
#else
m_comboBox_filament = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, AMS_MATERIALS_SETTING_COMBOX_WIDTH, 0, nullptr, wxCB_READONLY);
#endif
m_star_filled = ScalableBitmap(this, "score_star_light", 16);
m_star_empty = ScalableBitmap(this, "score_star_outline", 16);
m_sizer_filament->Add(m_comboBox_filament, 1, wxALIGN_CENTER, 0);
@@ -420,6 +438,7 @@ void AMSMaterialsSetting::paintEvent(wxPaintEvent &evt)
AMSMaterialsSetting::~AMSMaterialsSetting()
{
m_comboBox_filament->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_filament), NULL, this);
m_comboBox_filament->Unbind(EVT_DROPDOWN_ITEM_ACTION, &AMSMaterialsSetting::on_toggle_favorite_filament, this);
m_comboBox_cali_result->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_cali_result), NULL, this);
}
@@ -531,10 +550,9 @@ void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
std::string selected_ams_id;
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle) {
const FilamentInfos* selected_info = selected_filament_info();
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
std::string filament_id = filament_item.filament_id;
if (it->filament_id.compare(filament_id) == 0) {
if (selected_info && it->filament_id.compare(selected_info->filament_id) == 0) {
selected_ams_id = it->filament_id;
break;
}
@@ -682,12 +700,13 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
ams_setting_id = "";
// the combobox item
auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
const FilamentInfos* selected_info = selected_filament_info();
const std::string filament_id = selected_info ? selected_info->filament_id : std::string();
// check filament info (rack-gated per-nozzle blacklist evaluation)
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
const auto& fila_check_res = sCheckFilamentInfo(preset_bundle, obj, ams_id, slot_id,
filament_item.filament_id, ams_filament_id, ams_setting_id);
filament_id, ams_filament_id, ams_setting_id);
if (const auto& prohibit_items = fila_check_res.get_items_by_action("prohibition"); !prohibit_items.empty()) {
wxString info_msg;
@@ -986,6 +1005,133 @@ static void _collect_filament_info(const wxString& shown_name,
query_filament_types[shown_name] = filament.config.get_filament_type();
}
static const char* AMS_FAVORITE_FILAMENT_IDS_KEY = "ams_favorite_filament_ids";
const AMSMaterialsSetting::FilamentInfos* AMSMaterialsSetting::selected_filament_info() const
{
const int selection = m_comboBox_filament->GetSelection();
if (selection < 0 || selection >= int(m_filament_list_items.size()) || !m_filament_list_items[selection].is_filament)
return nullptr;
return &m_filament_list_items[selection].filament;
}
std::set<std::string> AMSMaterialsSetting::load_favorite_filament_ids() const
{
std::set<std::string> favorite_ids;
std::vector<std::string> values;
const std::string serialized = wxGetApp().app_config->get(AMS_FAVORITE_FILAMENT_IDS_KEY);
if (!serialized.empty() && Slic3r::unescape_strings_cstyle(serialized, values)) {
favorite_ids.insert(values.begin(), values.end());
}
return favorite_ids;
}
void AMSMaterialsSetting::save_favorite_filament_ids(const std::set<std::string>& favorite_ids)
{
std::vector<std::string> values(favorite_ids.begin(), favorite_ids.end());
wxGetApp().app_config->set(AMS_FAVORITE_FILAMENT_IDS_KEY, Slic3r::escape_strings_cstyle(values));
wxGetApp().app_config->save();
}
void AMSMaterialsSetting::rebuild_filament_dropdown_items(const std::string& selected_filament_id)
{
std::vector<DropDown::Item> items;
m_filament_list_items.clear();
auto append_header = [&items, this](const wxString& label) {
DropDown::Item item;
item.text = label;
item.style = DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED;
items.push_back(item);
m_filament_list_items.push_back(FilamentListItem{});
};
auto append_filament = [&items, this](const wxString& name) {
auto info_it = map_filament_items.find(name);
if (info_it == map_filament_items.end())
return;
DropDown::Item item;
item.text = name;
items.push_back(item);
m_filament_list_items.push_back(FilamentListItem{info_it->second, true});
};
// Favorites are listed again at the top under their own header. Without any, the list is the
// plain filament list.
const std::set<std::string> favorite_ids = load_favorite_filament_ids();
std::vector<wxString> favorites;
for (const wxString& name : m_sorted_filament_items) {
auto info_it = map_filament_items.find(name);
if (info_it != map_filament_items.end() && favorite_ids.count(info_it->second.filament_id) > 0)
favorites.push_back(name);
}
if (!favorites.empty()) {
append_header(_L("Favorite filaments"));
for (const wxString& name : favorites)
append_filament(name);
append_header(_L("All filaments"));
}
for (const wxString& name : m_sorted_filament_items)
append_filament(name);
int selection_idx = -1;
if (!selected_filament_id.empty()) {
for (int i = 0; i < int(m_filament_list_items.size()); ++i) {
if (m_filament_list_items[i].is_filament && m_filament_list_items[i].filament.filament_id == selected_filament_id) {
selection_idx = i;
break;
}
}
}
// Clear() first: SetItems() keeps the old selection, and SetSelection() ignores an index equal to it,
// which would leave the previous filament's name on the combo.
m_comboBox_filament->Clear();
m_comboBox_filament->SetItems(items);
update_favorite_stars();
m_comboBox_filament->SetSelection(selection_idx);
m_favorites_regroup = false;
}
void AMSMaterialsSetting::update_favorite_stars()
{
const std::set<std::string> favorite_ids = load_favorite_filament_ids();
for (size_t i = 0; i < m_filament_list_items.size(); ++i) {
const FilamentListItem& row = m_filament_list_items[i];
if (row.is_filament) {
const bool favorite = favorite_ids.count(row.filament.filament_id) > 0;
m_comboBox_filament->SetItemActionBitmap(i, favorite ? m_star_filled.bmp() : m_star_empty.bmp());
}
}
}
void AMSMaterialsSetting::toggle_favorite_filament(const std::string& filament_id)
{
if (filament_id.empty())
return;
std::set<std::string> favorite_ids = load_favorite_filament_ids();
auto iter = favorite_ids.find(filament_id);
if (iter == favorite_ids.end())
favorite_ids.insert(filament_id);
else
favorite_ids.erase(iter);
save_favorite_filament_ids(favorite_ids);
}
void AMSMaterialsSetting::on_toggle_favorite_filament(wxCommandEvent& evt)
{
const int index = evt.GetInt();
if (index < 0 || index >= int(m_filament_list_items.size()) || !m_filament_list_items[index].is_filament)
return;
toggle_favorite_filament(m_filament_list_items[index].filament.filament_id);
update_favorite_stars();
m_favorites_regroup = true;
}
void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_min, wxString temp_max, wxString k, wxString n)
{
if (!obj) return;
@@ -999,13 +1145,12 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
m_input_k_val->GetTextCtrl()->SetValue(k);
m_input_n_val->GetTextCtrl()->SetValue(n);
wxArrayString filament_items;
wxString bambu_filament_name;
wxString hint_filament_name; // the hint type to be selected
std::unordered_map<wxString, wxString> query_filament_vendors;// some information for sort
std::unordered_map<wxString, wxString> query_filament_types; //
std::set<std::string> filament_id_set;
m_sorted_filament_items.clear();
// The alias keyed map has to start empty: it is a member, so a stale alias left by an earlier
// popup (a different printer, a different nozzle) would resolve to that printer's filament_id.
map_filament_items.clear();
@@ -1035,7 +1180,7 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
if (alias.empty())
continue;
filament_items.push_back(alias);
m_sorted_filament_items.push_back(alias);
_collect_filament_info(alias, *filament_it, query_filament_vendors, query_filament_types);
FilamentInfos filament_infos;
@@ -1044,7 +1189,6 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
map_filament_items[alias] = filament_infos;
if (filament_it->filament_id == ams_filament_id) {
hint_filament_name = from_u8(alias);
bambu_filament_name = from_u8(alias);
@@ -1215,24 +1359,11 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
return name_compare != 0 ? name_compare < 0 : left < right;
};
std::sort(filament_items.begin(), filament_items.end(), _filament_sorter);
std::sort(m_sorted_filament_items.begin(), m_sorted_filament_items.end(), _filament_sorter);
}
// traverse the hint selection idx
int selection_idx = -1;
{
for(int i = 0; i < filament_items.size(); i++)
{
if (hint_filament_name == filament_items[i])
{
selection_idx = i;
break;
}
}
}
m_comboBox_filament->Set(filament_items);
m_comboBox_filament->SetSelection(selection_idx);
rebuild_filament_dropdown_items(ams_filament_id);
int selection_idx = m_comboBox_filament->GetSelection();
post_select_event(selection_idx);
if (selection_idx < 0) {
@@ -1289,6 +1420,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
int* from_printer = static_cast<int*>(m_comboBox_filament->GetClientData());
m_filament_type = "";
const FilamentInfos* selected_info = selected_filament_info();
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
if (preset_bundle) {
std::ostringstream stream;
@@ -1309,9 +1441,8 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
std::string nozzle_diameter_str = stream.str();
// Resolve the selection against the same list Popup() built the dropdown from, so the two
// halves of the dialog cannot disagree about which filaments this machine can use.
const std::string selected = m_comboBox_filament->GetValue().ToStdString();
if (!selected.empty()) {
const std::string filament_id = map_filament_items[selected].filament_id;
if (selected_info) {
const std::string& filament_id = selected_info->filament_id;
for (Preset *it : preset_bundle->get_filament_presets_for_machine(
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) {
if (it->filament_id != filament_id)
@@ -1364,7 +1495,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
//reset cali
int cali_select_idx = -1;
if ( !this->obj || m_filament_selection < 0) {
if ( !this->obj || m_filament_selection < 0 || !selected_info) {
m_input_k_val->Enable(false);
m_input_n_val->Enable(false);
m_button_confirm->Disable(); // ORCA No need to change style
@@ -1383,21 +1514,9 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
ams_filament_id = "";
ams_setting_id = "";
if (preset_bundle) {
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
auto itor = map_filament_items.find(m_comboBox_filament->GetValue().ToStdString());
if ( itor != map_filament_items.end()) {
ams_filament_id = itor->second.filament_id;
ams_setting_id = itor->second.setting_id;
break;
}
if (it->alias.compare(m_comboBox_filament->GetValue().ToStdString()) == 0) {
ams_filament_id = it->filament_id;
ams_setting_id = it->setting_id;
break;
}
}
if (selected_info) {
ams_filament_id = selected_info->filament_id;
ams_setting_id = selected_info->setting_id;
}
wxArrayString items;
@@ -1529,6 +1648,11 @@ void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect)
m_button_reset->Rescale(); // ORCA
m_button_confirm->Rescale(); // ORCA
m_button_close->Rescale(); // ORCA
// An open list would keep the measurements of the old stars, so close it before replacing them.
m_comboBox_filament->CloseDropdown();
m_star_filled.msw_rescale();
m_star_empty.msw_rescale();
update_favorite_stars();
this->Refresh();
}
+21 -6
View File
@@ -31,6 +31,7 @@
#include "slic3r/GUI/Widgets/StateColor.hpp"
#include <wx/panel.h>
#include <map>
#include <set>
#define AMS_MATERIALS_SETTING_DEF_COLOUR wxColour(255, 255, 255)
#define AMS_MATERIALS_SETTING_GREY900 wxColour(38, 46, 48)
@@ -156,6 +157,7 @@ protected:
void create_panel_kn(wxWindow* parent);
void on_dpi_changed(const wxRect &suggested_rect) override;
void on_select_filament(wxCommandEvent& evt);
void on_toggle_favorite_filament(wxCommandEvent& evt);
void on_select_cali_result(wxCommandEvent &evt);
void on_select_ok(wxCommandEvent &event);
void on_select_reset(wxCommandEvent &event);
@@ -163,6 +165,11 @@ protected:
void on_clr_picker(wxMouseEvent &event);
bool is_virtual_tray();
void update_widgets();
void rebuild_filament_dropdown_items(const std::string& selected_filament_id);
void update_favorite_stars();
std::set<std::string> load_favorite_filament_ids() const;
void save_favorite_filament_ids(const std::set<std::string>& favorite_ids);
void toggle_favorite_filament(const std::string& filament_id);
void update_filament_editing(bool is_printing);
// Orca: the variant index of the filament's per-variant options on the nozzle this tray feeds
@@ -202,12 +209,7 @@ protected:
int m_pa_cali_select_id = 0;
// Orca: native wxComboBox on macOS (the custom ComboBox mis-renders the filament dropdown there)
#ifdef __APPLE__
wxComboBox *m_comboBox_filament;
#else
ComboBox *m_comboBox_filament;
#endif
ComboBox * m_comboBox_cali_result;
TextInput* m_readonly_filament;
@@ -215,7 +217,20 @@ protected:
std::string filament_id;
std::string setting_id;
};
std::map<std::string, FilamentInfos> map_filament_items;
struct FilamentListItem {
FilamentInfos filament;
bool is_filament{false};
};
const FilamentInfos* selected_filament_info() const;
// Keyed by the same wxString the dropdown shows, so a row's name never has to be converted back.
std::map<wxString, FilamentInfos> map_filament_items;
std::vector<wxString> m_sorted_filament_items;
std::vector<FilamentListItem> m_filament_list_items;
ScalableBitmap m_star_filled;
ScalableBitmap m_star_empty;
// Favorites changed while the list was open; their rows are regrouped before it opens again.
bool m_favorites_regroup{false};
};
wxDECLARE_EVENT(EVT_SELECTED_COLOR, wxCommandEvent);
+54
View File
@@ -12,6 +12,7 @@
#include "DeviceManager.hpp"
#include "HMS.hpp"
#include "PresetBundleDialog.hpp"
#include "LegacyPrinterMigrationDialog.hpp"
#include "WebUserLoginDialog.hpp"
#include "WebViewDialog.hpp"
#include "slic3r/Utils/BBLCloudServiceAgent.hpp"
@@ -1109,6 +1110,8 @@ void GUI_App::post_init()
}
this->check_new_version_sf();
if (!cw_showed)
this->check_legacy_printer_migration(false);
const auto cloud_provider = get_printer_cloud_provider();
if (is_user_login(cloud_provider) && !app_config->get_stealth_mode()) {
// this->check_privacy_version(0);
@@ -3232,9 +3235,16 @@ bool GUI_App::on_init_inner()
#ifdef _MSW_DARK_MODE
#ifndef __WINDOWS__
// The label colours and the dark mode flag were set above from the saved mode. When the system theme
// differs from it, refresh them, or dialogs that map their colours stay in the old mode for this session.
const bool saved_dark_mode = dark_mode();
wxSystemAppearance app = wxSystemSettings::GetAppearance();
GUI::wxGetApp().app_config->set("dark_color_mode", app.IsDark() ? "1" : "0");
GUI::wxGetApp().app_config->save();
if (dark_mode() != saved_dark_mode) {
init_label_colours();
Update_dark_mode_flag();
}
#endif // __APPLE__
@@ -6815,6 +6825,50 @@ void GUI_App::reload_settings()
}
}
void GUI_App::check_legacy_printer_migration(bool by_user)
{
const std::vector<LegacyPrinterMigration> migrations = preset_bundle->legacy_printer_migrations();
if (migrations.empty()) {
if (by_user)
MessageDialog(mainframe, _L("All your filament presets are available on the printers they were made for."),
_L("Migrate Filament Presets"), wxOK | wxICON_INFORMATION).ShowModal();
return;
}
if (!by_user && app_config->get_bool("legacy_printer_migration_dont_remind"))
return;
LegacyPrinterMigrationDialog dlg(mainframe, migrations, !by_user);
const bool migrate = dlg.ShowModal() == wxID_YES;
if (dlg.dont_remind())
app_config->set_bool("legacy_printer_migration_dont_remind", true);
if (!migrate)
return;
std::vector<std::string> failed;
const std::string backup = preset_bundle->migrate_legacy_printers(migrations, failed);
if (backup.empty()) {
show_error(mainframe, _L("Your presets could not be backed up, so they were not migrated."));
return;
}
preset_bundle->update_compatible(PresetSelectCompatibleType::Never);
mainframe->update_side_preset_ui();
for (auto tab : tabs_list) {
tab->reload_config();
tab->update_changed_ui();
}
if (plater_)
plater_->sidebar().update_all_preset_comboboxes();
wxString message = format_wxstr(_L("Your filament presets were migrated. Select them again in the filament list where you used them.\n\n"
"A backup of your presets was saved to:\n%1%"), backup);
if (!failed.empty()) {
message += "\n\n" + _L("These presets could not be migrated:");
for (const std::string &name : failed)
message += "\n" + from_u8(name);
}
MessageDialog(mainframe, message, _L("Migrate Filament Presets"), wxOK | (failed.empty() ? wxICON_INFORMATION : wxICON_WARNING)).ShowModal();
}
//BBS reload when logout
void GUI_App::remove_user_presets()
{
+4
View File
@@ -568,6 +568,10 @@ public:
void check_update(bool show_tips, int by_user);
void check_new_version(bool show_tips = false, int by_user = 0);
void check_new_version_sf(bool show_tips = false, int by_user = 0);
// Offers to migrate user filament presets that lost printers their parent listed in an older release.
// At startup it asks once per release unless the user chose not to be reminded; by_user also reports
// when there is nothing to migrate.
void check_legacy_printer_migration(bool by_user);
bool process_network_msg(std::string dev_id, std::string msg);
void request_new_version(int by_user);
void enter_force_upgrade();
+11 -8
View File
@@ -1617,7 +1617,7 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me
int obj_idx, vol_idx;
get_selected_item_indexes(obj_idx, vol_idx, item);
if (obj_idx != -1) {
Plater::TakeSnapshot(plater, _u8L("Shift objects to bed"));
Plater::TakeSnapshot snapshot(plater, _u8L("Shift objects to bed"));
(*m_objects)[obj_idx]->ensure_on_bed();
cnv->reload_scene(true, true);
update_info_items(obj_idx);
@@ -2779,12 +2779,13 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
GLCanvas3D* cnv = plater->canvas3D();
switch (type) {
case InfoItemType::CustomSupports:
case InfoItemType::CustomSupports: {
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, "Remove support painting");
Plater::TakeSnapshot snapshot(plater, "Remove support painting");
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->supported_facets.reset();
break;
}
// BBS: remove CustomSeam
@@ -2795,19 +2796,21 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
}
break;
case InfoItemType::MmSegmentation:
case InfoItemType::MmSegmentation: {
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, "Remove color painting");
Plater::TakeSnapshot snapshot(plater, "Remove color painting");
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->mmu_segmentation_facets.reset();
break;
}
case InfoItemType::FuzzySkin:
case InfoItemType::FuzzySkin: {
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _u8L("Remove paint-on fuzzy skin"));
Plater::TakeSnapshot snapshot(plater, _u8L("Remove paint-on fuzzy skin"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->fuzzy_skin_facets.reset();
break;
}
// BBS: remove Sinking
case InfoItemType::Undef : assert(false); break;
@@ -6718,7 +6721,7 @@ void ObjectList::OnEditingStarted(wxDataViewEvent &event)
else if (col == colSinking) {
Plater * plater = wxGetApp().plater();
GLCanvas3D *cnv = plater->canvas3D();
Plater::TakeSnapshot(plater, _u8L("Shift objects to bed"));
Plater::TakeSnapshot snapshot(plater, _u8L("Shift objects to bed"));
int obj_idx, vol_idx;
get_selected_item_indexes(obj_idx, vol_idx, item);
(*m_objects)[obj_idx]->ensure_on_bed();
@@ -0,0 +1,96 @@
#include "LegacyPrinterMigrationDialog.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_Utils.hpp"
#include "I18N.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include "libslic3r/Preset.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/CheckBox.hpp"
#include "Widgets/DialogButtons.hpp"
#include "Widgets/Label.hpp"
#include <wx/event.h>
#include <wx/gdicmn.h>
#include <wx/sizer.h>
#include <wx/string.h>
#include <wx/textctrl.h>
#include <wx/toplevel.h>
#include <string>
#include <vector>
namespace Slic3r { namespace GUI {
LegacyPrinterMigrationDialog::LegacyPrinterMigrationDialog(wxWindow *parent, const std::vector<LegacyPrinterMigration> &migrations,
bool offer_dont_remind)
: DPIDialog(parent ? parent : static_cast<wxWindow *>(wxGetApp().mainframe), wxID_ANY, _L("Migrate Filament Presets"),
wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
{
SetBackgroundColour(*wxWHITE);
SetFont(Label::Body_14);
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *message = new Label(this,
_L("Since OrcaSlicer 2.5.0, filament presets have been restructured to support more features. Some of your "
"presets need to be migrated to the new structure, or they will no longer be available for some of your "
"printers.\n\n"
"Would you like to migrate them now? Your presets will be backed up first, and this only needs to be done "
"once. You can also do it later from Help > Migrate Filament Presets."),
0);
message->Wrap(FromDIP(520));
sizer->Add(message, 0, wxEXPAND | wxALL, FromDIP(15));
sizer->Add(new Label(this, _L("Affected presets:")), 0, wxLEFT | wxRIGHT, FromDIP(15));
sizer->AddSpacer(FromDIP(5));
wxString details;
for (const LegacyPrinterMigration &migration : migrations) {
details += format_wxstr(_L("%1% (based on %2%)"), migration.preset, migration.parent) + "\n";
for (const std::string &printer : migration.printers)
details += " " + from_u8(printer) + "\n";
}
auto *list = new wxTextCtrl(this, wxID_ANY, details, wxDefaultPosition, wxSize(-1, FromDIP(220)),
wxTE_MULTILINE | wxTE_READONLY | wxTE_DONTWRAP);
sizer->Add(list, 1, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(15));
if (offer_dont_remind) {
auto *dont_remind_sizer = new wxBoxSizer(wxHORIZONTAL);
m_dont_remind = new ::CheckBox(this);
auto *dont_remind_label = new Label(this, _L("Don't remind me again"));
dont_remind_label->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) {
m_dont_remind->SetValue(!m_dont_remind->GetValue());
e.Skip();
});
dont_remind_sizer->Add(m_dont_remind, 0, wxALIGN_CENTER_VERTICAL);
dont_remind_sizer->Add(dont_remind_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
sizer->Add(dont_remind_sizer, 0, wxLEFT | wxRIGHT | wxTOP, FromDIP(15));
}
auto *buttons = new DialogButtons(this, {"Yes", "No"}, "Yes");
buttons->GetYES()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { EndModal(wxID_YES); });
buttons->GetNO()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { EndModal(wxID_NO); });
sizer->Add(buttons, 0, wxEXPAND);
SetSizerAndFit(sizer);
CenterOnParent();
wxGetApp().UpdateDlgDarkUI(this);
}
bool LegacyPrinterMigrationDialog::dont_remind() const
{
return m_dont_remind != nullptr && m_dont_remind->GetValue();
}
void LegacyPrinterMigrationDialog::on_dpi_changed(const wxRect &)
{
if (m_dont_remind != nullptr)
m_dont_remind->Rescale();
GetSizer()->SetSizeHints(this);
Refresh();
}
}} // namespace Slic3r::GUI
@@ -0,0 +1,34 @@
#pragma once
#include "GUI_Utils.hpp"
#include <vector>
class CheckBox;
class wxWindow;
class wxRect;
namespace Slic3r {
struct LegacyPrinterMigration;
namespace GUI {
// Lists the user filament presets that no longer show on printers their parent listed in an older
// release, and asks whether to migrate them. ShowModal() returns wxID_YES to migrate. With
// offer_dont_remind it also offers to stop the startup reminder for good.
class LegacyPrinterMigrationDialog : public DPIDialog
{
public:
LegacyPrinterMigrationDialog(wxWindow *parent, const std::vector<LegacyPrinterMigration> &migrations, bool offer_dont_remind);
bool dont_remind() const;
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;
private:
::CheckBox *m_dont_remind{nullptr};
};
}} // namespace Slic3r::GUI
+4
View File
@@ -2827,6 +2827,10 @@ wxMenu* MainFrame::generate_help_menu()
append_menu_item(helpMenu, wxID_ANY, _L("Show Configuration Folder"), _L("Show Configuration Folder"),
[](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); });
append_menu_item(helpMenu, wxID_ANY, _L("Migrate Filament Presets"),
_L("Bring back filament presets that a profile update removed from printers they were made for"),
[](wxCommandEvent&) { wxGetApp().check_legacy_printer_migration(true); });
helpMenu->AppendSeparator();
// Troubleshoot center
+4
View File
@@ -589,6 +589,10 @@ std::vector<NativeCommand> build_command_catalog()
Slic3r::GUI::desktop_open_datadir_folder();
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_migrate_filament_presets", L("Migrate Filament Presets"), L("Help"), [](const std::string&) {
wxGetApp().check_legacy_printer_migration(true);
return AppActionRunResult{AppActionRunResult::Level::Success};
});
add("help_troubleshoot", L("Troubleshoot Center"), L("Help"), [](const std::string&) {
wxGetApp().troubleshoot();
return AppActionRunResult{AppActionRunResult::Level::Success};
+29 -9
View File
@@ -82,6 +82,11 @@ ComboBox::ComboBox(wxWindow *parent,
e.SetId(GetId());
GetEventHandler()->ProcessEvent(e);
});
drop.Bind(EVT_DROPDOWN_ITEM_ACTION, [this](wxCommandEvent &e) {
e.SetEventObject(this);
e.SetId(GetId());
GetEventHandler()->ProcessEvent(e);
});
drop.Bind(EVT_DISMISS, [this](auto &) {
drop_down = false;
wxCommandEvent e(wxEVT_COMBOBOX_CLOSEUP);
@@ -251,7 +256,7 @@ int ComboBox::Append(const wxString &text,
int style)
{
auto valid_bit_map = bitmap.IsOk() ? bitmap : wxNullBitmap;
Item item{text, wxEmptyString, valid_bit_map, valid_bit_map, clientData, group_key, group_label};
Item item{text, wxEmptyString, valid_bit_map, valid_bit_map, wxNullBitmap, clientData, group_key, group_label};
item.style = style;
items.push_back(item);
SetClientDataType(wxClientData_Void);
@@ -297,6 +302,12 @@ void ComboBox::ForceDropdownOpen()
}
}
void ComboBox::CloseDropdown()
{
if (drop.IsShown())
drop.DismissAndNotify();
}
unsigned int ComboBox::GetCount() const { return items.size(); }
void ComboBox::set_replace_text(wxString text, wxString image_name)
@@ -350,6 +361,13 @@ void ComboBox::SetItemBitmap(unsigned int n, wxBitmap const &bitmap)
drop.Invalidate();
}
void ComboBox::SetItemActionBitmap(unsigned int n, wxBitmap const &bitmap)
{
if (n >= items.size()) return;
items[n].action_icon = bitmap.IsOk() ? bitmap : wxNullBitmap;
drop.Invalidate();
}
int ComboBox::DoInsertItems(const wxArrayStringsAdapter &items,
unsigned int pos,
void ** clientData,
@@ -357,7 +375,7 @@ int ComboBox::DoInsertItems(const wxArrayStringsAdapter &items,
{
if (pos > this->items.size()) return -1;
for (int i = 0; i < items.GetCount(); ++i) {
Item item { items[i], wxEmptyString, wxNullBitmap, wxNullBitmap, clientData ? clientData[i] : NULL };
Item item { items[i], wxEmptyString, wxNullBitmap, wxNullBitmap, wxNullBitmap, clientData ? clientData[i] : NULL };
this->items.insert(this->items.begin() + pos, item);
++pos;
}
@@ -419,16 +437,18 @@ void ComboBox::keyDown(wxKeyEvent& event)
case WXK_UP:
case WXK_DOWN:
case WXK_LEFT:
case WXK_RIGHT:
if ((event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_LEFT) && GetSelection() > 0) {
SetSelection(GetSelection() - 1);
} else if ((event.GetKeyCode() == WXK_DOWN || event.GetKeyCode() == WXK_RIGHT) && GetSelection() + 1 < items.size()) {
SetSelection(GetSelection() + 1);
} else {
case WXK_RIGHT: {
// Step over disabled rows: the mouse cannot select them either.
const int step = (event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_LEFT) ? -1 : 1;
int n = GetSelection() + step;
while (n >= 0 && n < int(items.size()) && (items[n].style & DD_ITEM_STYLE_DISABLED))
n += step;
if (n < 0 || n >= int(items.size()))
break;
}
SetSelection(n);
sendComboBoxEvent();
break;
}
case WXK_TAB:
HandleAsNavigationKey(event);
break;
+2
View File
@@ -96,10 +96,12 @@ public:
wxBitmap GetItemBitmap(unsigned int n);
void SetItemBitmap(unsigned int n, wxBitmap const &bitmap);
void SetItemActionBitmap(unsigned int n, wxBitmap const &bitmap);
bool is_drop_down(){return drop_down;}
void DeleteOneItem(unsigned int pos) { DoDeleteOneItem(pos); }
void ForceDropdownOpen();
void CloseDropdown();
protected:
virtual int DoInsertItems(const wxArrayStringsAdapter &items,
+64 -4
View File
@@ -30,6 +30,7 @@
#include "slic3r/GUI/wxExtensions.hpp"
wxDEFINE_EVENT(EVT_DISMISS, wxCommandEvent);
wxDEFINE_EVENT(EVT_DROPDOWN_ITEM_ACTION, wxCommandEvent);
BEGIN_EVENT_TABLE(DropDown, PopupWindow)
@@ -242,6 +243,19 @@ static wxSize GetBmpSize(wxBitmap & bmp)
#endif
}
// The x-range [first, second) of the column at the right of the rows that holds the items' action
// icons, empty when no item has one. render() draws the icons in it and mouseReleased() hit-tests
// clicks against it, so the drawn icon and its clickable area always match.
std::pair<int, int> DropDown::actionColumn() const
{
if (actionIconWidth <= 0)
return {0, 0};
int right = rowSize.x;
if (rowSize.y * count > GetSize().y) // position bar
right -= 6;
return {right - actionIconWidth - FromDIP(16), right};
}
static void _DrawSplitItem(const wxWindow* w, wxDC& dc, wxString split_text, wxPoint start_pt, int item_width, int item_height)
{
// save dc
@@ -371,6 +385,10 @@ void DropDown::render(wxDC &dc)
rcContent.width -= szBmp.x + 5;
}
const auto action_column = actionColumn();
if (action_column.second > action_column.first)
rcContent.width = action_column.first - rcContent.x;
std::set<wxString> groups;
// draw texts & icons
int index = 0;
@@ -434,13 +452,24 @@ void DropDown::render(wxDC &dc)
auto text = group.IsEmpty()
? (item.group_key.IsEmpty() ? item.text : item.group_label)
: item.text;
if (item.action_icon.IsOk()) {
auto action_size = GetBmpSize(item.action_icon);
wxPoint action_pt(action_column.second - action_size.x - FromDIP(8),
rcContent.y + (rcContent.height - action_size.y) / 2);
dc.DrawBitmap(item.action_icon, action_pt);
}
if (!text_off && !text.IsEmpty()) {
wxSize tSize = dc.GetMultiLineTextExtent(text);
if (pt.x + tSize.x > rcContent.GetRight()) {
if (is_hover && item.tip.IsEmpty())
SetToolTip(text);
text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END,
rcContent.GetRight() - pt.x);
int max_text_width = rcContent.GetRight() - pt.x;
if (max_text_width <= 0) {
text.clear();
} else {
if (is_hover && item.tip.IsEmpty())
SetToolTip(text);
text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, max_text_width);
}
}
pt.y += (rcContent.height - textSize.y) / 2;
dc.SetFont(GetFont());
@@ -529,6 +558,7 @@ void DropDown::messureSize()
if (!need_sync) return;
textSize = wxSize();
iconSize = wxSize();
actionIconWidth = 0;
count = 0;
wxClientDC dc(GetParent() ? GetParent() : this);
dc.SetFont(GetFont());
@@ -565,8 +595,13 @@ void DropDown::messureSize()
size1.x += size2.x + (text_off ? 0 : 5);
}
}
if (item.action_icon.IsOk())
actionIconWidth = std::max(actionIconWidth, GetBmpSize(item.action_icon).x);
if (size1.x > textSize.x) textSize = size1;
}
// Every row leaves room for the action column, as render() and actionColumn() lay it out.
if (actionIconWidth > 0)
textSize.x += actionIconWidth + FromDIP(16);
if (!align_icon) iconSize.x = 0;
wxSize szContent = textSize;
if (szContent.x < FromDIP(120) && !use_content_width)
@@ -636,6 +671,11 @@ void DropDown::messureSize()
e.SetId(GetId());
GetEventHandler()->ProcessEvent(e);
});
subDropDown->Bind(EVT_DROPDOWN_ITEM_ACTION, [this](wxCommandEvent &e) {
e.SetEventObject(this);
e.SetId(GetId());
GetEventHandler()->ProcessEvent(e);
});
}
need_sync = false;
}
@@ -714,6 +754,15 @@ void DropDown::mouseReleased(wxMouseEvent& event)
if (hover_item < 0)
return;
if (hover_item >= 0 && (subDropDown == nullptr || subDropDown->group.empty())) { // not moved
int index = hoverIndex();
if (index >= 0 && items[index].action_icon.IsOk()) {
const auto action_column = actionColumn();
if (event.GetX() >= action_column.first && event.GetX() < action_column.second) {
sendDropDownActionEvent(index);
paintNow();
return;
}
}
sendDropDownEvent();
if (mainDropDown)
mainDropDown->hover_item = -1; // To Dismiss mainDropDown
@@ -830,6 +879,17 @@ void DropDown::sendDropDownEvent()
GetEventHandler()->ProcessEvent(event);
}
void DropDown::sendDropDownActionEvent(int index)
{
if (index < 0 || index >= int(items.size()) || (items[index].style & DD_ITEM_STYLE_DISABLED))
return;
wxCommandEvent event(EVT_DROPDOWN_ITEM_ACTION, GetId());
event.SetEventObject(this);
event.SetInt(index);
event.SetString(items[index].text);
GetEventHandler()->ProcessEvent(event);
}
void DropDown::Dismiss()
{
if (subDropDown && subDropDown->IsShown())
+6
View File
@@ -5,6 +5,7 @@
#include <wx/event.h>
#include <vector>
#include <cstddef>
#include <utility>
#include <wx/gdicmn.h>
#include "slic3r/GUI/Widgets/StateColor.hpp"
#include <boost/date_time/posix_time/ptime.hpp>
@@ -27,6 +28,7 @@ class wxWindow;
#define DD_ITEM_STYLE_DIMMED 0x0004 // gray text, but still selectable
wxDECLARE_EVENT(EVT_DISMISS, wxCommandEvent);
wxDECLARE_EVENT(EVT_DROPDOWN_ITEM_ACTION, wxCommandEvent);
class DropDown : public PopupWindow
{
@@ -37,6 +39,7 @@ public:
wxString text_static_tips;// display static tips for TextInput.eg.PrinterInfoBox
wxBitmap icon;
wxBitmap icon_textctrl;// display icon for TextInput.eg.PrinterInfoBox
wxBitmap action_icon;
void * data{nullptr};
wxString group_key{};
wxString group_label{};
@@ -65,6 +68,7 @@ private:
wxSize textSize;
wxSize iconSize;
int actionIconWidth = 0;
wxSize rowSize;
StateHandler state_handler;
@@ -135,6 +139,7 @@ private:
int hoverIndex();
int selectedItem();
std::pair<int, int> actionColumn() const;
friend class ComboBox;
void messureSize();
@@ -148,6 +153,7 @@ private:
void mouseWheelMoved(wxMouseEvent &event);
void sendDropDownEvent();
void sendDropDownActionEvent(int index);
DECLARE_EVENT_TABLE()
-27
View File
@@ -69,10 +69,6 @@ webkit_web_view_run_javascript_finish (WebKitWebView
GError **error);
WEBKIT_API void
webkit_javascript_result_unref (WebKitJavascriptResult *js_result);
WEBKIT_API guint
webkit_get_major_version (void);
WEBKIT_API guint
webkit_get_minor_version (void);
}
#endif
@@ -405,29 +401,6 @@ bool WebView::NeedsRecreateOnShow()
return false;
#endif
}
bool WebView::NeedsCompositingWorkaround()
{
#ifdef __linux__
const guint major = webkit_get_major_version();
const guint minor = webkit_get_minor_version();
return major < 2 || (major == 2 && minor < 42);
#else
return false;
#endif
}
bool WebView::SupportsForceSharedMemory()
{
#ifdef __linux__
const guint major = webkit_get_major_version();
const guint minor = webkit_get_minor_version();
return major > 2 || (major == 2 && minor >= 44);
#else
return false;
#endif
}
#if wxUSE_WEBVIEW_EDGE
bool WebView::CheckWebViewRuntime()
{
-10
View File
@@ -30,16 +30,6 @@ public:
// ignoring every navigation. A panel that gets true here recreates its view on first Show().
static bool NeedsRecreateOnShow();
// True when the linked WebKitGTK predates 2.42, where accelerated compositing
// could fail silently on XWayland sessions, so it is forced off as a fallback.
// False on every other platform. Safe to call before GTK initialises.
static bool NeedsCompositingWorkaround();
// True when the linked WebKitGTK is new enough (>= 2.44) to honour
// WEBKIT_DMABUF_RENDERER_FORCE_SHM, the shared-memory transport switch.
// False on every other platform. Safe to call before GTK initialises.
static bool SupportsForceSharedMemory();
static void RecreateAll();
};
+37
View File
@@ -2043,3 +2043,40 @@ TEST_CASE("3MF XML entries declaring more than an int can hold fail to load", "[
CHECK_FALSE(parser.check_3mf_from_prusa(path));
}
}
TEST_CASE("Loading a gcode.3mf from a stream keeps the plate's pick and pattern file paths", "[3mf][Regression]")
{
Model model;
REQUIRE(load_stl((std::string(TEST_DATA_DIR) + "/test_3mf/Prusa.stl").c_str(), &model));
model.add_default_instances();
ScopedTemporaryDir backup_dir("orca_pick");
model.set_backup_path(backup_dir.string());
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
auto plate = std::make_unique<PlateData>();
plate->plate_index = 0;
plate->pick_file = (backup_dir.path() / "pick.png").string();
plate->pattern_bbox_file = (backup_dir.path() / "plate.json").string();
ScopedTemporaryFile temp(".3mf");
const std::string test_file = temp.string();
StoreParams store_params;
store_params.path = test_file.c_str();
store_params.model = &model;
store_params.config = &config;
store_params.plate_data_list.push_back(plate.get());
store_params.strategy = SaveStrategy::Zip64 | SaveStrategy::Silence;
REQUIRE(store_bbs_3mf(store_params));
boost::nowide::ifstream in(test_file, std::ios::binary);
Model dst_model;
DynamicPrintConfig dst_config;
PlateDataPtrs dst_plates;
Semver file_version;
dst_model.set_backup_path(backup_dir.string());
REQUIRE(load_gcode_3mf_from_stream(in, &dst_config, &dst_model, &dst_plates, &file_version));
REQUIRE(dst_plates.size() == 1);
CHECK(dst_plates.front()->pick_file == "Metadata/pick_1.png");
CHECK(dst_plates.front()->pattern_bbox_file == "Metadata/plate_1.json");
release_PlateData_list(dst_plates);
}
@@ -37,6 +37,7 @@
#include <utility>
#include "libslic3r/Config.hpp"
#include <map>
#include <regex>
#include "libslic3r/libslic3r.h"
#include "libslic3r/PublishSettings.hpp"
#include <set>
@@ -7161,3 +7162,126 @@ TEST_CASE("A user filament whose derived compatible printer cannot be written ba
CHECK(read_file(file) == before);
}
#endif
TEST_CASE("User filaments that lost printers their narrowed system parent listed in an older release are found", "[Preset][Bundle]")
{
PresetBundle bundle;
PresetCollection &filaments = bundle.filaments;
const std::string parent = "Generic PETG @Creality K1-all";
// load_preset inserts in sorted order, so presets are looked up again rather than held by reference.
auto set_printers = [&filaments](const std::string &name, std::vector<std::string> printers) {
filaments.find_preset(name)->config.option<ConfigOptionStrings>("compatible_printers", true)->values = std::move(printers);
};
add_inmemory_preset(filaments, parent).is_system = true;
add_inmemory_preset(filaments, "My PETG", parent);
add_inmemory_preset(filaments, "My PETG own list", parent);
set_printers(parent, { "Creality K1C 0.6 nozzle" });
set_printers("My PETG", { "Creality K1C 0.6 nozzle" });
set_printers("My PETG own list", { "Creality K1C 0.6 nozzle", "Creality K1C 0.8 nozzle" });
// A printer the parent still lists is not reported again. The system preset and a user preset
// with its own list are left alone.
std::vector<LegacyPrinterMigration> migrations =
filaments.legacy_printer_migrations({ { parent, { "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle" } } });
REQUIRE(migrations.size() == 1);
CHECK(migrations[0].preset == "My PETG");
CHECK(migrations[0].parent == parent);
CHECK(migrations[0].printers == std::vector<std::string>{ "Creality K1C 0.4 nozzle" });
// A parent renamed after the map was written is still found under its old name.
add_inmemory_preset(filaments, "Generic PETG @Creality K1 renamed").is_system = true;
add_inmemory_preset(filaments, "My renamed PETG", "Generic PETG @Creality K1 renamed");
set_renamed_from(filaments, "Generic PETG @Creality K1 renamed", { parent });
set_printers("Generic PETG @Creality K1 renamed", { "Creality K1C 0.6 nozzle" });
set_printers("My renamed PETG", { "Creality K1C 0.6 nozzle" });
migrations = filaments.legacy_printer_migrations({ { parent, { "Creality K1C 0.4 nozzle" } } });
REQUIRE(migrations.size() == 2);
CHECK(migrations[1].preset == "My renamed PETG");
CHECK(migrations[1].parent == "Generic PETG @Creality K1 renamed");
}
TEST_CASE("Migrating a user filament backs up the user folder to a zip and writes the legacy printers to its file", "[Preset][Bundle]")
{
ScopedTemporaryDir temp_dir;
ScopedDataDir data(temp_dir.path());
const std::string parent = "Generic PETG @Creality K1-all";
auto with_parent = [&parent](PresetBundle &bundle) {
add_inmemory_preset(bundle.filaments, parent).is_system = true;
bundle.filaments.find_preset(parent)->config.option<ConfigOptionStrings>("compatible_printers", true)->values = { "Creality K1C 0.6 nozzle" };
};
// Like a real user preset, the file states only its diff, so the printer list comes from the parent.
const fs::path user_dir = temp_dir.path() / PRESET_USER_DIR / "default";
const fs::path file = user_dir / PRESET_FILAMENT_NAME / "My PETG.json";
fs::create_directories(file.parent_path());
const std::string original = std::string(R"({"type":"filament","name":"My PETG","from":"User","version":"1.0.0","inherits":")") + parent + R"("})";
std::ofstream(file.string()) << original;
auto load = [&user_dir](PresetCollection &filaments) {
PresetsConfigSubstitutions substitutions;
filaments.load_presets(user_dir.string(), PRESET_FILAMENT_NAME, substitutions, ForwardCompatibilitySubstitutionRule::EnableSilent);
};
PresetBundle bundle;
PresetCollection &filaments = bundle.filaments;
with_parent(bundle);
load(filaments);
const std::map<std::string, std::vector<std::string>> map{ { parent, { "Creality K1C 0.4 nozzle" } } };
const std::vector<LegacyPrinterMigration> migrations = filaments.legacy_printer_migrations(map);
REQUIRE(migrations.size() == 1);
std::vector<std::string> failed;
const std::string backup = bundle.migrate_legacy_printers(migrations, failed);
REQUIRE_FALSE(backup.empty());
CHECK(std::regex_match(fs::path(backup).filename().string(), std::regex("filament_preset_migration_\\d{8}T\\d{6}Z_backup\\.zip")));
CHECK(failed.empty());
const fs::path extracted = temp_dir.path() / "extracted";
REQUIRE(extract_archive_confined(backup, extracted.string()));
CHECK(read_file(extracted / PRESET_USER_DIR / "default" / PRESET_FILAMENT_NAME / "My PETG.json") == original);
// Reloaded from disk, the preset carries both printers itself and needs no further migration.
PresetBundle reloaded;
with_parent(reloaded);
load(reloaded.filaments);
const std::vector<std::string> expected{ "Creality K1C 0.6 nozzle", "Creality K1C 0.4 nozzle" };
CHECK(reloaded.filaments.find_preset("My PETG")->config.option<ConfigOptionStrings>("compatible_printers")->values == expected);
CHECK(reloaded.filaments.legacy_printer_migrations(map).empty());
CHECK_FALSE(reloaded.filaments.apply_legacy_printer_migration(migrations[0]));
}
namespace {
// A filament collection that exposes the rename map update, which PresetBundle runs after loading system presets.
struct RenameTestFilaments : public PresetCollection
{
RenameTestFilaments()
: PresetCollection(Preset::TYPE_FILAMENT, Preset::filament_options(),
static_cast<const PrintRegionConfig &>(FullPrintConfig::defaults()))
{}
using PresetCollection::update_map_system_profile_renamed;
};
} // namespace
TEST_CASE("Migrating a user filament keeps the old name of its renamed parent", "[Preset][Bundle]")
{
ScopedTemporaryDir temp_dir;
RenameTestFilaments filaments;
const std::string parent = "Generic PETG @Creality K1-all";
const std::string old_parent = "Creality Generic PETG @K1-all";
add_inmemory_preset(filaments, parent).is_system = true;
filaments.find_preset(parent)->config.option<ConfigOptionStrings>("compatible_printers", true)->values = { "Creality K1C 0.6 nozzle" };
set_renamed_from(filaments, parent, { old_parent });
filaments.update_map_system_profile_renamed();
const fs::path file = temp_dir.path() / PRESET_FILAMENT_NAME / "My PETG.json";
fs::create_directories(file.parent_path());
std::ofstream(file.string()) << R"({"type":"filament","name":"My PETG","from":"User","version":"1.0.0","inherits":")" << old_parent << R"("})";
PresetsConfigSubstitutions substitutions;
filaments.load_presets(temp_dir.path().string(), PRESET_FILAMENT_NAME, substitutions, ForwardCompatibilitySubstitutionRule::EnableSilent);
const std::vector<LegacyPrinterMigration> migrations = filaments.legacy_printer_migrations({ { parent, { "Creality K1C 0.4 nozzle" } } });
REQUIRE(migrations.size() == 1);
REQUIRE(filaments.apply_legacy_printer_migration(migrations[0]));
// An older release knows the parent only by its old name.
const std::string saved = read_file(file);
CHECK(saved.find("\"inherits\": \"" + old_parent + "\"") != std::string::npos);
CHECK(saved.find("Creality K1C 0.4 nozzle") != std::string::npos);
}
+1 -1
View File
@@ -255,7 +255,7 @@ TEST_CASE("Native command catalog includes the Help and developer-mode commands"
const Slic3r::GUI::NativeCommand* first = find("help_keyboard_shortcuts");
REQUIRE(first != nullptr);
for (const char* key : {"help_setup_wizard", "help_open_config_folder", "help_troubleshoot", "help_network_test",
for (const char* key : {"help_setup_wizard", "help_open_config_folder", "help_migrate_filament_presets", "help_troubleshoot", "help_network_test",
"help_benchmark_3d_scene", "help_tip_of_the_day", "help_check_updates", "help_about", "open_wiki",
"open_youtube"}) {
const Slic3r::GUI::NativeCommand* c = find(key);