Compare commits

..
Author SHA1 Message Date
Ian Chua f38f2bddec fix: test fixture 2026-10-16 22:23:42 +08:00
Ian Chua 1d291f6f04 fix: clang-tidy 2026-10-16 21:28:37 +08:00
Ian Chua 5390b789f1 fix: nozzle & preset sync 2026-10-16 20:51:22 +08:00
Ian Chua f68fbfed58 feat: update happy hare parsing 2026-10-16 20:48:57 +08:00
Ian Chua 7ed4095b20 fix: clang-tidy 2026-10-15 16:06:50 +08:00
Ian Chua ee1cc1b227 Merge branch 'feat/ams-parsing' into feat/generic-extruders-and-ams 2026-10-15 16:05:00 +08:00
Ian Chua 46096416cd Merge branch 'main' into feat/generic-n-extruders 2026-10-15 16:04:30 +08:00
Ian Chua 74fe2b7996 Merge branch 'main' into feat/generic-n-extruders 2026-10-15 14:45:26 +08:00
Ian Chua 38f649fc00 Merge branch 'main' into feat/ams-parsing 2026-10-15 14:42:59 +08:00
peachismomo 23f6771dcb fix: parse AFC lane data and non-bambu ams tray indices correctly 2026-10-11 20:11:47 +08:00
Ian Chua aebc0a2979 fix: change ams type when parsing AFC mmu 2026-10-09 18:27:14 +08:00
Ian Chua 6f0ec60d21 feat: update happy hare parsing 2026-10-08 21:26:00 +08:00
Ian Chua 8e43a2ac13 fix: center external spool 2026-10-08 21:19:00 +08:00
Ian Chua 6b366ad8a9 fix: sync_ams_list should drop placeholder filaments 2026-10-08 20:42:11 +08:00
Ian Chua 7d8261668e fix: use extruder count for bbl and m_nozzle_book for non bbl 2026-10-08 20:29:47 +08:00
Ian Chua 00127beffa fix: use multi-nozzle AMS layout for non-Bambu printers at any extruder count 2026-10-08 19:26:43 +08:00
Lam Wei Lun 927eb9922c Map slot id directly 2026-10-08 18:37:09 +08:00
Ian Chua 3d62769664 feat: expand filament parsing in MoonrakerPrinterAgent 2026-10-08 17:46:01 +08:00
Ian Chua e5e30ee0e8 fix: harden generic N-extruder device model and status/AMS UI, add N-toolhead ExtruderImage sizing, tests, and HLSD 2026-10-08 17:39:44 +08:00
Ian Chua 7515fb480f fix: remove dead code 2026-10-08 15:56:20 +08:00
Ian Chua acc1d6fd21 Merge branch 'main' into feat/generic-n-extruders 2026-10-08 14:49:50 +08:00
Ian Chua 6ca0fc5ef1 fix: clang-tidy 2026-10-07 19:33:58 +08:00
Ian Chua bcf7d078cd Merge branch 'main' into feat/generic-n-extruders 2026-10-07 19:25:52 +08:00
Ian Chua 15fc96fca7 Merge branch 'main' into feat/generic-n-extruders 2026-10-07 19:25:35 +08:00
Ian Chua 1fffda0533 chore: add tests and HLSD 2026-10-07 19:05:06 +08:00
Ian Chua 33e26a127e fix: warn when toolhead count differs from the selected printer preset's nozzle_diameter count 2026-10-07 18:40:41 +08:00
Ian Chua 6dd0cb9eba fix: harden generic N-extruder device paths 2026-10-07 18:30:49 +08:00
Ian Chua 9bf0fc2a8e test: reset virtual trays in the generic AMS tray index map case 2026-10-06 20:53:46 +08:00
Ian Chua 5bf6db95b3 fix: clang-tidy 2026-10-06 19:56:53 +08:00
Ian Chua 0feb87b417 feat: generic N extruders, multiple AMS and AMS slots 2026-10-06 19:22:10 +08:00
143 changed files with 10738 additions and 44808 deletions
@@ -569,18 +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, and sets `WEBKIT_DISABLE_COMPOSITING_MODE=1` (non-replacing) inside that branch when the linked
WebKitGTK is older than 2.42: a forced X11 backend in a Wayland session is the only default-path case
that runs through XWayland, which `DISPLAY` and `WAYLAND_DISPLAY` alone do not tell you (a native
Wayland app inherits both). It 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 the no-EGL default path where Orca forces
`GDK_BACKEND=x11`, 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,69 +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`, and in that same branch it sets
`WEBKIT_DISABLE_COMPOSITING_MODE=1` non-replacing when the linked WebKitGTK predates 2.42
(`WebView::NeedsCompositingWorkaround()`). That forced X11 backend in a Wayland session is the only
default-path case that really runs through XWayland; `DISPLAY` and `WAYLAND_DISPLAY` alone do not
identify it, because a native Wayland app inherits both. `XInitThreads()` runs only when `DISPLAY` is
set.
`WAYLAND_DISPLAY` is set, it forces `GDK_BACKEND=x11`; it sets `WEBKIT_DISABLE_COMPOSITING_MODE=1`
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 the branch where wx
lacks EGL and Orca forces `GDK_BACKEND=x11`, 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
- **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. 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,
// and only in the branch that forces GDK_BACKEND=x11 (wx without EGL), the
// case that runs through XWayland
if (WebView::NeedsCompositingWorkaround() && 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 -15
View File
@@ -22,7 +22,7 @@ function usage() {
echo " -g: run the requested build steps inside a Docker/Podman Ubuntu 24.04 container similar to the GitHub Actions Linux runner"
echo " -h: prints this help text"
echo " -i: build the Orca Slicer AppImage (optional)"
echo " -p: disable precompiled headers (on by default): more ccache hits after header changes, slower misses"
echo " -p: boost ccache hit rate by disabling precompiled headers (default: ON)"
echo " -r: skip RAM and disk checks (low RAM compiling)"
echo " -s: build the Orca Slicer (optional)"
echo " -t: build tests (optional), requires -s flag"
@@ -549,20 +549,6 @@ fi
if [ -n "${CMAKE_CCACHE}" ] ; then
echo "${CMAKE_CCACHE} found, enabling compiler caching..."
export CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER="${CMAKE_CCACHE}" -DCMAKE_CXX_COMPILER_LAUNCHER="${CMAKE_CCACHE}")
# The same ccache settings as the CI build. Without this sloppiness ccache refuses every
# compile that uses the precompiled header, which is most of OrcaSlicer, and skips headers
# a checkout has just written. It is added to the sloppiness already configured, and
# CCACHE_SLOPPINESS or CCACHE_(NO)DEPEND set in the environment win.
if [[ "$(basename "${CMAKE_CCACHE}")" == ccache ]] ; then
if [[ -z "${CCACHE_SLOPPINESS+x}" ]] ; then
CCACHE_SLOPPINESS=$("${CMAKE_CCACHE}" --get-config sloppiness 2>/dev/null || true)
export CCACHE_SLOPPINESS="${CCACHE_SLOPPINESS:+${CCACHE_SLOPPINESS},}pch_defines,time_macros,include_file_mtime,include_file_ctime"
fi
# Hash the includes the compiler reports instead of preprocessing every miss.
if [[ -z "${CCACHE_DEPEND+x}${CCACHE_NODEPEND+x}" ]] ; then
export CCACHE_DEPEND=1
fi
fi
else
echo "Note: ccache or sccache are not found. Install either of them for faster rebuilds."
fi
+17 -2
View File
@@ -1,7 +1,22 @@
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
if (JPEG_VERSION STREQUAL "6")
message("Using Jpeg Lib 62")
set(jpeg_flag "")
elseif (JPEG_VERSION STREQUAL "7")
message("Using Jpeg Lib 70")
set(jpeg_flag "-DWITH_JPEG7=ON")
else ()
message("Using Jpeg Lib 80")
set(jpeg_flag "-DWITH_JPEG8=ON")
endif ()
endif()
orcaslicer_add_cmake_project(JPEG
URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.2.0.zip
URL_HASH SHA256=1b4c2cb6cba1ca5e53b9e85eeeab02154cab4b70894c96e97a9c747a26d19d63
URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.0.1.zip
URL_HASH SHA256=d6d99e693366bc03897677650e8b2dfa76b5d6c54e2c9e70c03f0af821b0a52f
DEPENDS ${ZLIB_PKG}
CMAKE_ARGS
-DENABLE_SHARED=OFF
-DENABLE_STATIC=ON
${jpeg_flag}
)
+233
View File
@@ -0,0 +1,233 @@
# Multi-extruder and multi-AMS device control
`DeviceManager`/`MachineObject` model the toolheads, AMS units, and filament slots of a connected
printer and drive the device-control UI from that model. The model represents the counts as
device-reported runtime values rather than compile-time caps, so a printer with an arbitrary number
of toolheads, several AMS units, and a non-uniform number of slots per unit is handled by the same
code paths. The printer agent is responsible for translating its printer's state into the shared
JSON dialect this model consumes; the agent boundary is described in
[printer-agent.md](printer-agent.md), and this document describes the dialect and the model.
## Model layout
`MachineObject` owns the device subsystems that this document covers:
- `DevExtderSystem` — the toolheads (extruders) and their per-toolhead filament/temperature state.
- `DevFilaSystem` — the AMS units (`DevAms`) and their trays (`DevAmsTray`).
- `DevNozzleSystem` — the nozzles and, on rack printers, `DevNozzleRack`.
- `DevFilaSwitch` — a filament switch that can feed several AMS units to more than one extruder.
Each subsystem exposes its own count — `DevExtderSystem::GetTotalExtderCount()`,
`DevFilaSystem::GetAmsCount()`, `DevAms::GetSlotCount()` — and the rest of the application derives
its layout from those values rather than from fixed assumptions.
## Wire dialect
The dialect is the Bambu-shaped JSON that `DeviceManager` and `MachineObject` already consume. An
agent whose printer speaks something else is expected to translate into it; the agent is not free
to invent a new shape. Two representations coexist for extruders, and one for AMS/slots.
### Extruders
The generic representation is `print.device.extruder`, parsed by
`ExtderSystemParser::ParseV2_0` (`DevExtruderSystem.cpp`). It has a packed `state` integer and an
`info` array with one object per toolhead:
```text
device.extruder.state packed integer
bits 0..3 total extruder count
bits 4..7 current extruder id
bits 8..11 target extruder id
bits 12..14 switch state
bits 15..18 currently loading extruder id
bit 19 busy for loading
device.extruder.info[] one entry per toolhead
id extruder id
filam_bak array of backup-group bits
info bit 1 has filament, bit 2 buffer has filament, bit 3 nozzle present
temp bits 0..15 current temperature, bits 16..31 target temperature
spre slot_id bits 0..7, ams_id bits 8..15 (previous)
snow same packing (current)
star same packing (target)
stat bit 0..15 AMS status, bits 16..31 RFID status
hnow current nozzle id
```
The legacy single/dual representation is the pair of scalar fields `nozzle_temper` /
`nozzle_target_temper`, plus the `print.ams.tray_tar` / `tray_now` tray pointer, parsed by
`ExtderSystemParser::ParseV1_0`. That parser is a fallback and only fills the main extruder when the
reported toolhead count is one.
Physical extruder ids are fixed (`MAIN_EXTRUDER_ID = 0`, `DEPUTY_EXTRUDER_ID = 1`). Bambu printers
present the two toolheads as a left/right pair and invert the physical-to-logical mapping; generic
printers use the toolhead id itself as the logical id. `DevNozzle::GetLogicExtruderId()` /
`GetExtruderId()` centralise that distinction.
### AMS units and slots
AMS units arrive as `print.ams.ams[]` and are parsed by `DevFilaSystemParser::ParseV1_0`. Each unit
carries an `id` and an `info` bitfield:
```text
info bits 0..3 type: 0 dummy, 1 AMS, 2 AMS-Lite, 3 N3F, 4 N3S, 5 AMS-Lite-mixed (N9)
info bits 8..11 bound extruder id (0xE marks a switch-bound unit)
info bits 24..27 switch inlet when the unit is switch-bound
```
`DevAmsType` (`DevDefs.h`) enumerates the resulting unit kinds: `EXT_SPOOL`, `AMS`, `AMS_LITE`,
`N3F`, `N3S`, and the `AMS_LITE_MIXED` N9 variant. `AMS_LITE_MIXED` reports itself as `AMS_LITE` to
generic callers; code that needs the distinction uses `IsAmsLiteMixed()`.
A unit whose bound-extruder nibble is `0xE` is switch-bound: it is kept only when a `DevFilaSwitch`
is installed, in which case its extruder id is pinned to the main extruder and its binding set
becomes `{0, 1}`; otherwise the unit is dropped from the AMS list.
Each unit owns a map of `DevAmsTray` keyed by the tray id from the payload. The slot count is a
property of the unit kind and the reported trays:
- `AMS`, `AMS_LITE`, and `N3F` report four slots on Bambu printers, matching the firmware contract;
- `N3S` reports one slot;
- `EXT_SPOOL` and any non-Bambu unit report the number of trays actually present.
The tray ids are the payload's slot indices; they are expected to be unique, 0-based, and contiguous
within a unit, because the generic tray-index mapping below is cumulative.
### External spool slots
External/manual spools are virtual trays in `MachineObject::vt_slot`, separate from the trays owned
by `DevFilaSystem`. Their stable id is `vt_id = VIRTUAL_TRAY_MAIN_ID - extruder_id`: 255 for
extruder 0, 254 for extruder 1, then 253, 252, and so on. The index in `vt_slot` is therefore
`VIRTUAL_TRAY_MAIN_ID - vt_id`, not the order in which a printer happens to report trays.
For non-Bambu printers, slot topology comes from the live `DevExtderSystem` extruder count, not from
external-spool telemetry. `MachineObject::ensure_external_virtual_tray_slots()` grows `vt_slot` to
one entry per extruder and enables virtual-tray support even when the agent reports no `vir_slot`,
`vt_tray`, or external-spool data. It only adds missing entries, so reported tray metadata is kept.
The shared ensure runs after push-status parsing has updated the extruder count and after the generic
AMS/agent-filament parsers. Thus agents may report external-spool metadata, but do not have to invent
empty slots just to make each toolhead's external-spool row visible.
When a payload does report virtual trays, `vir_slot` entries are placed by their id and missing
indices are filled with placeholders. Generic agent `external` entries are likewise placed by
extruder id, so sparse or out-of-order reports do not shift a spool onto another toolhead. An empty
placeholder represents the slot's existence, not a claim that the printer reported a loaded spool.
Bambu keeps its fixed 255/254 virtual-tray pair and does not use generic slot synthesis.
## Mapping
Several subsystems relate AMS units, trays, and extruders, and more than one place derives a tray
index. Bambu mapping computes `ams_id * 4 + slot_id` for its fixed-width units; generic mapping in
`DevFilaSystem` and the sidebar matcher uses the count of lanes in preceding units plus the slot id.
The two agree for Bambu units and differ for a non-Bambu unit whose slot count is not four.
### AMS id to extruder id
`DevFilaSystem::GetExtruderIdByAmsId()` resolves an AMS id to the extruder it feeds:
- an AMS unit returns its bound extruder (`DevAms::GetExtruderId()`);
- a virtual tray returns `VIRTUAL_TRAY_MAIN_ID - vt_id`;
- Bambu's virtual AMS ids map to the main/deputy extruder.
A unit may be bound to more than one extruder when a `DevFilaSwitch` feeds it; the binding set
(`DevAms::m_binded_extruder_set`) and switch inlet are read by switch-aware code while the
single-extruder id stays pinned for legacy callers.
### Tray index
`DevFilaSystem::GetTrayIndexMap()` maps a global tray index to an `(ams_id, slot_id)` pair and is
used for backup-slot translation, calibration addressing, and the mapping popups.
- Bambu AMS: `ams_id * 4 + slot_id`; N3S: the AMS id; AMS-Lite-mixed (N9): `24 + slot_id`.
- Generic (non-Bambu): a running lane index — the count of trays in preceding units plus the slot
id. This depends on each unit's tray ids being 0-based and contiguous.
The sidebar sync matcher uses the same identities. Bambu retains its fixed left/right AMS and
external-spool choices. For a generic printer, a full sync considers AMS units according to their
bound extruder ids and considers every external slot by `255 - vt_id`; a split left/right mapping
still restricts itself to the corresponding legacy extruders 0 and 1. Generic AMS lanes use the
cumulative tray index above, while their `ams_id`/`slot_id` pair remains the address used to update
the matching sidebar entry. The runtime filament-list configs also carry a transient device-extruder
association so generic entries are grouped under the correct toolhead after sync; that association
is removed before the configs enter the slicing pipeline. Bambu keeps the existing key-based
left/right grouping.
### Virtual-slot recognition
`devPrinterUtil::IsVirtualSlot()` decides whether an id denotes a virtual tray. On Bambu printers it
is the fixed pair `{255, 254}`. On generic printers it is the descending range
`[255 - count + 1, 255]`, where `count` is the selected printer preset's extruder count. Slot
topology is grown from the live device extruder count, while this recognition range still uses the
preset count; the preset is expected to match the connected device. A mismatch can therefore make
the recognized virtual-id range disagree with `vt_slot`, and is reported by the existing count
warning rather than silently accepted.
## Device-control UI
The device-control widgets follow the same device-reported counts.
- `AMSModel` (`AMSItem.hpp`) mirrors `DevAmsType` numerically: `EXT_AMS`, `GENERIC_AMS`, `AMS_LITE`,
`N3F_AMS`, `N3S_AMS`. A plain `AMS` maps to `GENERIC_AMS`.
- On non-Bambu printers `use_generic_ams_layout()` routes `GENERIC_AMS` units through a variable-lane
rendering that draws one lane per reported tray; Bambu keeps its fixed four-slot rendering.
- `AMSControl` picks its layout by toolhead count, not vendor: `CreateAmsSingleNozzle` for one
toolhead, `CreateAmsDoubleNozzle` for two, and `CreateAmsMultiNozzle` (one preview and one AMS
simplebook per toolhead) for three or more. A two-toolhead generic printer therefore keeps the
legacy left/right presentation; that is deliberate.
- The status panel builds external-spool entries from `MachineObject::vt_slot`; generic slot
synthesis keeps one empty entry available for each live toolhead even when the printer does not
report external-spool state.
- The status panel's `ExtruderImage` renders one per-toolhead state per nozzle and is resized to
`GetTotalExtderCount()`. Non-Bambu printers choose the active toolhead through
`m_generic_nozzle_selector` (shown when more than one toolhead exists); Bambu keeps its left/right
`m_nozzle_btn_panel`. Nozzle temperature controls are created on demand per toolhead.
## Constraints
- **Single dialect.** The agent translates its protocol into this JSON; the model does not learn
vendor protocols. Extending the model with a new unit kind or field means extending the dialect.
- **Runtime counts.** The agent reports toolhead count and AMS unit/tray data. The shared model derives
generic external-spool slot count from the parsed live toolhead count; nothing caps the number of
toolheads or generic slots except the id range and the Bambu contract described above.
- **Extruder dialect invariants.** `device.extruder.state`'s count field must equal the length of
`info[]`, and `info[]` must be ordered by extruder id with `id` equal to the array index: the
parser places toolheads by array position while `GetExtderById()` indexes by id.
- **One filament road per toolhead.** Each toolhead renders a single AMS filament road rather than an
independent per-toolhead road state. A generic scaffold that allowed the latter was removed as
unused; the one-extruder-per-toolhead road is the intended model.
- **Preset/device agreement.** The generic layout and virtual-slot range derive their count from the
selected printer preset. A printer preset must therefore declare the same number of
`nozzle_diameter` entries as the device has toolheads. A mismatch is surfaced only as a log warning
(`StatusPanel::check_extruder_count_mismatch()`), deliberately log-only, because the sidebar and
extruder logic would otherwise silently disagree.
- **Bambu compatibility.** Bambu-specific presentation (left/right pair, fixed 4-slot units, fixed
virtual ids, physical/logical inversion) is preserved behind the vendor check. Generic paths do
not alter it.
- **Tray-index invariant.** The generic tray-index map is cumulative, so payload tray ids must be
unique, 0-based, and contiguous per unit. A malformed id is a producer error.
- **Protocol markers.** The dialect has no neutral equivalent for some Bambu state, so agents that
lack it send empty `print.cfg`, `print.fun`, `print.aux`, and `print.stat`. Their presence selects
the new-protocol parse path; they are compatibility markers, not feature switches.
## Main implementation locations
- [`DevExtruderSystem`](../../src/slic3r/GUI/DeviceCore/DevExtruderSystem.h) — toolhead model and the
`device.extruder` parser
- [`DevFilaSystem`](../../src/slic3r/GUI/DeviceCore/DevFilaSystem.h) — AMS/tray model, the AMS parser,
and the mapping helpers
- [`DevMapping.cpp`](../../src/slic3r/GUI/DeviceCore/DevMapping.cpp) — filament-to-device tray matching
for Bambu and generic external/AMS slots
- [`PresetBundle.cpp`](../../src/libslic3r/PresetBundle.cpp) — per-extruder grouping of the transient
device filament list
- [`DevDefs.h`](../../src/slic3r/GUI/DeviceCore/DevDefs.h) — `DevAmsType`, extruder ids, and virtual
tray ids
- [`DeviceManager.cpp`](../../src/slic3r/GUI/DeviceManager.cpp) — message dispatch, `vir_slot`
parsing, external-slot topology, and device state assembly
- [`AMSControl.cpp`](../../src/slic3r/GUI/Widgets/AMSControl.cpp) — per-toolhead AMS panes
(`CreateAmsMultiNozzle`) and the generic layout
- [`AMSItem.hpp`](../../src/slic3r/GUI/Widgets/AMSItem.hpp) — `AMSModel` and the variable-lane AMS
rendering
- [`StatusPanel.cpp`](../../src/slic3r/GUI/StatusPanel.cpp) — `ExtruderImage`, the toolhead selector,
and per-toolhead temperature controls
- [`MoonrakerPrinterAgent.cpp`](../../src/slic3r/Utils/MoonrakerPrinterAgent.cpp) — producer example
(AMS payload and the `device.extruder` encoding)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><rect x="1.5" y="3.5" width="12" height="8" rx="1" ry="1" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/><path d="M3,5h1v1h-1Z M5,5h1v1h-1Z M7,5h1v1h-1Z M9,5h1v1h-1Z M11,5h1v1h-1Z M4,7h1v1h-1Z M6,7h1v1h-1Z M8,7h1v1h-1Z M10,7h1v1h-1Z M5,9h5v1h-5Z" style="fill:#fefefe;"/></svg>
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><rect x="7" y="12" width="1" height="1" style="fill:#fefefe;"/><path d="M4.5,5.5c0-1.71,1.43-3.09,3.16-3,1.62.08,2.84,1.54,2.84,3.17h0c0,.61-.25,1.2-.69,1.62l-2.31,2.24v.97" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/></svg>

Before

Width:  |  Height:  |  Size: 415 B

After

Width:  |  Height:  |  Size: 366 B

@@ -592,10 +592,6 @@
"name": "SUNLU PETG @base",
"sub_path": "filament/SUNLU/SUNLU PETG @base.json"
},
{
"name": "School House Spools PETG @base",
"sub_path": "filament/SchoolHouseSpools/School House Spools PETG @base.json"
},
{
"name": "eSUN PETG @base",
"sub_path": "filament/eSUN/eSUN PETG @base.json"
@@ -1100,10 +1096,6 @@
"name": "SUNLU Wood PLA @base",
"sub_path": "filament/SUNLU/SUNLU Wood PLA @base.json"
},
{
"name": "School House Spools PLA @base",
"sub_path": "filament/SchoolHouseSpools/School House Spools PLA @base.json"
},
{
"name": "Valment PLA @base",
"sub_path": "filament/Valment/Valment PLA @base.json"
@@ -2268,10 +2260,6 @@
"name": "SUNLU PETG @System",
"sub_path": "filament/SUNLU/SUNLU PETG @System.json"
},
{
"name": "School House Spools PETG @System",
"sub_path": "filament/SchoolHouseSpools/School House Spools PETG @System.json"
},
{
"name": "eSUN PETG @System",
"sub_path": "filament/eSUN/eSUN PETG @System.json"
@@ -3824,10 +3812,6 @@
"name": "SUNLU Wood PLA @System",
"sub_path": "filament/SUNLU/SUNLU Wood PLA @System.json"
},
{
"name": "School House Spools PLA @System",
"sub_path": "filament/SchoolHouseSpools/School House Spools PLA @System.json"
},
{
"name": "Valment PLA @System",
"sub_path": "filament/Valment/Valment PLA @System.json"
@@ -1,18 +0,0 @@
{
"type": "filament",
"name": "School House Spools PETG @System",
"inherits": "School House Spools PETG @base",
"from": "system",
"setting_id": "RSu2orfhR8ktT36h",
"instantiation": "true",
"filament_max_volumetric_speed": [
"13"
],
"filament_long_retractions_when_cut": [
"1"
],
"filament_retraction_distances_when_cut": [
"18"
],
"compatible_printers": []
}
@@ -1,80 +0,0 @@
{
"type": "filament",
"name": "School House Spools PETG @base",
"inherits": "fdm_filament_pet",
"from": "system",
"filament_id": "OFnaCGQ9",
"instantiation": "false",
"cool_plate_temp": [
"0"
],
"cool_plate_temp_initial_layer": [
"0"
],
"eng_plate_temp": [
"70"
],
"eng_plate_temp_initial_layer": [
"70"
],
"fan_cooling_layer_time": [
"30"
],
"fan_max_speed": [
"40"
],
"fan_min_speed": [
"10"
],
"filament_cost": [
"16.00"
],
"filament_density": [
"1.25"
],
"filament_flow_ratio": [
"0.95"
],
"filament_max_volumetric_speed": [
"8"
],
"filament_multitool_ramming_flow": [
"8"
],
"filament_vendor": [
"School House Spools"
],
"hot_plate_temp": [
"70"
],
"hot_plate_temp_initial_layer": [
"70"
],
"nozzle_temperature": [
"252"
],
"nozzle_temperature_initial_layer": [
"252"
],
"nozzle_temperature_range_high": [
"270"
],
"nozzle_temperature_range_low": [
"230"
],
"overhang_fan_speed": [
"90"
],
"overhang_fan_threshold": [
"10%"
],
"slow_down_layer_time": [
"12"
],
"textured_plate_temp": [
"70"
],
"textured_plate_temp_initial_layer": [
"70"
]
}
@@ -1,18 +0,0 @@
{
"type": "filament",
"name": "School House Spools PLA @System",
"inherits": "School House Spools PLA @base",
"from": "system",
"setting_id": "3rHJk6SVAvFzyHI0",
"instantiation": "true",
"filament_max_volumetric_speed": [
"21"
],
"filament_long_retractions_when_cut": [
"1"
],
"filament_retraction_distances_when_cut": [
"18"
],
"compatible_printers": []
}
@@ -1,35 +0,0 @@
{
"type": "filament",
"name": "School House Spools PLA @base",
"inherits": "fdm_filament_pla",
"from": "system",
"filament_id": "OFHzwmT9",
"instantiation": "false",
"filament_cost": [
"17.00"
],
"filament_density": [
"1.26"
],
"filament_flow_ratio": [
"0.98"
],
"filament_max_volumetric_speed": [
"12"
],
"filament_vendor": [
"School House Spools"
],
"filament_scarf_seam_type": [
"none"
],
"filament_scarf_height": [
"10%"
],
"filament_scarf_gap": [
"0%"
],
"filament_scarf_length": [
"10"
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "RatRig",
"version": "02.04.00.07",
"version": "02.04.00.06",
"force_update": "0",
"description": "RatRig configurations",
"machine_model_list": [
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -107,7 +107,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -107,7 +107,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -107,7 +107,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -106,7 +106,7 @@
"z_hop": [
"0.2"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
"change_filament_gcode": "M600",
@@ -131,7 +131,7 @@
"bed_exclude_area": [
"0x0"
],
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single]",
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single]",
"machine_end_gcode": "END_PRINT",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
@@ -18,7 +18,7 @@
<script src="../js/plugin-config-ui.js"></script>
<script src="./index.js"></script>
<script src="./plugin-sort.js"></script>
<script src="../../js/fuzzy-search.js"></script>
<script src="../js/fuzzy-search.js"></script>
<script src="./plugin-search.js"></script>
</head>
<body onLoad="OnInit()">
+2 -6
View File
@@ -156,16 +156,12 @@ function SetCloudMode( bOn )
m_CloudMode=bOn;
$("body").toggleClass("cloud-mode", bOn);
if( bOn )
{
$(".BtnItem").removeClass("BtnItemSelected");
$(".BtnShortcut").addClass("BtnItemSelected");
}
}
function GotoCloud()
{
$(".BtnItem").removeClass("BtnItemSelected");
$(".BtnShortcut").addClass("BtnItemSelected");
SetCloudMode(true);
SendSimpleCommand("homepage_open_cloud");
}
@@ -336,10 +336,10 @@ modules:
sha256: 3d22d46c566b1761a0e15ea397589b3a5f36ac09b7c785382e6470156c04247f
dest: external-packages/PNG
# libjpeg-turbo 3.2.0
# libjpeg-turbo 3.0.1
- type: file
url: https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.2.0.zip
sha256: 1b4c2cb6cba1ca5e53b9e85eeeab02154cab4b70894c96e97a9c747a26d19d63
url: https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.0.1.zip
sha256: d6d99e693366bc03897677650e8b2dfa76b5d6c54e2c9e70c03f0af821b0a52f
dest: external-packages/JPEG
# Freetype 2.12.1
+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
+15 -21
View File
@@ -163,7 +163,6 @@ using json = nlohmann::json;
#include <X11/Xlib.h>
#endif
#include <unistd.h>
#include "slic3r/GUI/Widgets/WebView.hpp"
#endif
#ifdef SLIC3R_GUI
@@ -1157,10 +1156,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 where it
// forces GDK_BACKEND=x11 below (a build without EGL, so the app really
// runs through XWayland), 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");
@@ -1168,14 +1165,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,18 +1204,23 @@ int CLI::run(int argc, char **argv)
if (wayland_env && *wayland_env) {
BOOST_LOG_TRIVIAL(warning) << "Wayland detected but wxWidgets has no EGL support (wxHAS_EGL is OFF). Forcing X11 backend.";
::setenv("GDK_BACKEND", "x11", true);
// GTK will use X11 in a Wayland session here, which is the
// one default-path case that really runs through XWayland.
// WebKit2GTK accelerated compositing could fail silently
// there on releases before 2.42, so force it off. Disabling
// it also turns off threaded scrolling, hence the gate.
if (WebView::NeedsCompositingWorkaround())
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false);
}
}
#endif
// 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 (display_env_wk && *display_env_wk && wayland_env_wk && *wayland_env_wk) {
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false);
}
}
// XInitThreads is needed before GStreamer may use Xlib. On
// native Wayland without DISPLAY, GStreamer uses waylandsink
// (no Xlib involved), so the call is skipped.
@@ -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
+16 -25
View File
@@ -432,36 +432,27 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
std::string OozePrevention::pre_toolchange(GCode& gcodegen)
{
std::string gcode;
unsigned int extruder_id = gcodegen.writer().filament()->id();
// Check if this tool will ever be used again in this print.
bool is_last_use = false;
if (gcodegen.m_print != nullptr && !gcodegen.m_print->tool_ordering().empty()) {
coordf_t current_z = gcodegen.layer() ? gcodegen.layer()->print_z : gcodegen.writer().get_position().z();
const auto& tool_ordering = gcodegen.m_print->tool_ordering();
const auto& cfg = gcodegen.config();
const int heater = imex_physical_heater_for(cfg.is_imex.value, cfg.physical_extruder_map, int(extruder_id));
is_last_use = tool_ordering.is_last_extrusion_layer(current_z, extruder_id);
for (unsigned int f : tool_ordering.all_extruders())
if (is_last_use && imex_physical_heater_for(cfg.is_imex.value, cfg.physical_extruder_map, int(f)) == heater)
is_last_use = tool_ordering.is_last_extrusion_layer(current_z, f);
}
int temp = 0;
if (!is_last_use) {
const auto& filament_idle_temp = gcodegen.config().idle_temperature;
if (filament_idle_temp.get_at(extruder_id) == 0) {
if (gcodegen.config().standby_temperature_delta.value == 0)
return std::string();
temp = this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value;
} else {
temp = filament_idle_temp.get_at(extruder_id);
}
}
std::string gcode = gcodegen.writer().set_temperature(temp, false, extruder_id);
// There is no idle temperature defined in filament settings.
// Use the delta value from print config.
if (gcodegen.config().standby_temperature_delta.value != 0) {
// we assume that heating is always slower than cooling, so no need to block
gcode += gcodegen.writer().set_temperature
(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, extruder_id);
gcode.pop_back();
gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed
}
} else {
// Use the value from filament settings. That one is absolute, not delta.
gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id);
gcode.pop_back();
gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed
}
return gcode;
}
-1
View File
@@ -1004,7 +1004,6 @@ protected:
size_t num_objects,
size_t num_islands);
friend class OozePrevention;
friend class Wipe;
friend class WipeTowerIntegration;
friend class PressureEqualizer;
+1 -14
View File
@@ -1276,13 +1276,6 @@ void ToolOrdering::collect_extruder_statistics(bool prime_multi_material)
sort_remove_duplicates(m_all_printing_extruders);
}
m_last_layer_per_extruder.clear();
for (const LayerTools& lt : m_layer_tools) {
for (unsigned int ext : lt.extruders) {
m_last_layer_per_extruder[ext] = lt.print_z;
}
}
if (prime_multi_material && ! m_all_printing_extruders.empty()) {
// Reorder m_all_printing_extruders in the sequence they will be primed, the last one will be m_first_printing_extruder.
// Then set m_first_printing_extruder to the 1st extruder primed.
@@ -3671,11 +3664,5 @@ int WipingExtrusions::get_support_interface_extruder_overrides(const PrintObject
return -1;
}
bool ToolOrdering::is_last_extrusion_layer(coordf_t print_z, unsigned int extruder_id) const
{
auto it = m_last_layer_per_extruder.find(extruder_id);
if (it == m_last_layer_per_extruder.end())
return true;
return print_z >= it->second - EPSILON;
}
} // namespace Slic3r
-3
View File
@@ -370,7 +370,6 @@ public:
std::vector<int> & initial_filaments);
bool has_non_support_filament(const PrintConfig &config);
bool is_last_extrusion_layer(coordf_t print_z, unsigned int extruder_id) const;
private:
void initialize_layers(std::vector<coordf_t> &zs);
@@ -414,8 +413,6 @@ private:
// which keeps every legacy per-object code path bit-identical (loops over an empty map are
// no-ops; downstream emission falls through to the per-object branch).
std::map<unsigned int, std::map<LayerTools::MixedSubLayerGroup::VolumeKey, std::vector<size_t>>> m_gradient_volume_layers;
// Map: extruder_id -> print_z (z coordinate) of the last layer it is used on
std::map<unsigned int, coordf_t> m_last_layer_per_extruder;
const PrintObject* m_print_object_ptr = nullptr;
Print* m_print;
bool m_sorted = false;
+158 -49
View File
@@ -4511,6 +4511,53 @@ static void warn_ambiguous_filament_id_match(const PresetCollection &filaments,
<< "\", also matches " << others;
}
// Orca: resolve a compatible system preset from a printer-reported filament type. Prefers an exact
// "Generic <type>" match, then the longest generic whose filament_type appears as a whole word in
// the reported type (e.g. "ASA" in "ASA Sparkle"). On success `generic_name_out` holds the
// "Generic <...>" name used for compatibility messages. Shared by the known-filament_id lookup and
// the data-bearing-but-unmapped tray path so both pick the same preset.
static PresetCollection::Iterator find_generic_preset_for_type(PresetCollection &filaments, const std::string &filament_type, std::string &generic_name_out)
{
if (filament_type.empty())
return filaments.end();
const std::string generic_name = "Generic " + filament_type;
auto iter = std::find_if(filaments.begin(), filaments.end(), [&generic_name](const Preset &f) {
return f.is_compatible && f.is_system && boost::algorithm::starts_with(f.name, generic_name);
});
if (iter != filaments.end()) {
generic_name_out = generic_name;
return iter;
}
// Similarity fallback: find a generic preset whose filament_type
// appears as a whole word in the reported type (e.g. "ASA" in "ASA Sparkle").
auto upper_type = boost::to_upper_copy(filament_type);
auto contains_word = [](const std::string& haystack, const std::string& needle) {
auto pos = haystack.find(needle);
while (pos != std::string::npos) {
bool start_ok = (pos == 0 || !std::isalnum(static_cast<unsigned char>(haystack[pos - 1])));
bool end_ok = (pos + needle.size() >= haystack.size() ||
!std::isalnum(static_cast<unsigned char>(haystack[pos + needle.size()])));
if (start_ok && end_ok)
return true;
pos = haystack.find(needle, pos + 1);
}
return false;
};
// Find the longest-matching preset type to prefer e.g. "PA-CF" over "PA".
size_t best_len = 0;
for (auto it = filaments.begin(); it != filaments.end(); ++it) {
if (!it->is_compatible || !it->is_system || !boost::algorithm::starts_with(it->name, "Generic "))
continue;
auto preset_type = boost::to_upper_copy(it->config.opt_string("filament_type", 0u));
if (preset_type.size() > best_len && contains_word(upper_type, preset_type)) {
iter = it;
best_len = preset_type.size();
generic_name_out = "Generic " + it->config.opt_string("filament_type", 0u);
}
}
return iter;
}
void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
{
combox_info.clear();
@@ -4566,7 +4613,9 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfig *,std::string>> &unknowns, bool use_map, std::map<int, AMSMapInfo> &maps, bool enable_append, MergeFilamentInfo &merge_info, bool color_only)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "use_map:" << use_map << " enable_append:" << enable_append;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": use_map %1% enable_append %2% color_only %3% start_presets %4% start_colors %5%")
% use_map % enable_append % color_only % this->filament_presets.size()
% project_config.option<ConfigOptionStrings>("filament_colour")->values.size();
std::vector<std::string> ams_filament_presets;
std::vector<std::string> ams_filament_colors;
std::vector<std::string> ams_filament_color_types;
@@ -4578,6 +4627,10 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
bool valid{false};
bool is_map{false};
bool is_placeholder{false};
// Orca: set for a tray that reports filament data (type and/or loaded flag) but carries
// no preset id. It is not `valid` (no id to map through), yet it still holds a real spool
// and is eligible for the same append step as a mapped tray.
bool has_reported_data{false};
std::string filament_color = "";
std::string filament_color_type = "";
std::string filament_preset = "";
@@ -4591,12 +4644,32 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
auto filament_id = ams.opt_string("filament_id", 0u);
auto filament_color = ams.opt_string("filament_colour", 0u);
auto filament_color_type = ams.opt_string("filament_colour_type", 0u);
auto filament_type = ams.opt_string("filament_type", 0u);
auto filament_exist = ams.has("filament_exist") && ams.opt_bool("filament_exist", 0u);
auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed");
auto filament_multi_color = ams.opt<ConfigOptionStrings>("filament_multi_colour")->values;
auto ams_id = ams.opt_string("ams_id", 0u);
auto slot_id = ams.opt_string("slot_id", 0u);
auto is_placeholder = ams.has("filament_slot_placeholder") && ams.opt_bool("filament_slot_placeholder", 0u);
ams_infos.push_back({filament_id.empty() ? false : true, false, is_placeholder, filament_color});
// Real reported filament data: the device named a type or flagged the slot loaded. A
// topology-only synthetic virtual tray carries neither, so it must not become a filament.
bool has_reported_data = !is_placeholder && filament_id.empty() && (!filament_type.empty() || filament_exist);
AmsInfo ams_info;
ams_info.valid = !filament_id.empty();
ams_info.is_placeholder = is_placeholder;
ams_info.has_reported_data = has_reported_data;
ams_info.filament_color = filament_color;
// Only the append path of a data-bearing, id-less tray reads these; the known-id path keeps
// taking its color and color type from ams_filament_colors/ams_filament_color_types.
if (has_reported_data) {
if (ams_info.filament_color.empty())
ams_info.filament_color = "#CECECE";
ams_info.filament_color_type = filament_color_type.empty() ? "1" : filament_color_type;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": entry %1% ams_id %2% slot_id %3% filament_id %4% type %5% color %6% valid %7% placeholder %8% reported_data %9% changed %10%")
% index % ams_id % slot_id % filament_id % filament_type % filament_color
% (!filament_id.empty()) % is_placeholder % has_reported_data % filament_changed;
ams_infos.push_back(std::move(ams_info));
AMSMapInfo temp = {ams_id, slot_id};
ams_array_maps.push_back(temp);
index++;
@@ -4607,6 +4680,46 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
maps.erase(j);
}
}
}
if (is_placeholder) {
// Orca: an empty AMS slot is a placeholder, not a filament - never synthesize a
// preset for it. Kept here only to keep index alignment with ams_infos; the sync
// drops it so no filament slot is created or retained for an empty tray.
ams_filament_presets.push_back("");
ams_filament_colors.push_back("");
ams_filament_color_types.push_back("");
ams_multi_color_filment.push_back({});
} else if (use_map) {
if (has_reported_data) {
// Orca: the tray reports a filament but no preset id, so it never reaches the
// filament_id lookup. Resolve the same generic preset the id path would pick
// from the reported type and keep the reported color; the append step below (if
// enabled) then turns it into a sidebar filament. Without a generic match the
// slot is dropped rather than invented, exactly like an empty tray.
std::string resolved_generic;
auto generic = find_generic_preset_for_type(filaments, filament_type, resolved_generic);
if (generic == filaments.end()) {
// Reported loaded but no usable type: fall back to the first compatible
// generic, matching the id path's fallback for an unknown model.
generic = std::find_if(filaments.begin(), filaments.end(), [](const Preset &f) {
return f.is_compatible && f.is_system && boost::algorithm::starts_with(f.name, "Generic ");
});
}
if (generic != filaments.end()) {
auto append_color = filament_color.empty() ? std::string("#CECECE") : filament_color;
ams_filament_presets.push_back(generic->name);
ams_filament_colors.push_back(append_color);
ams_filament_color_types.push_back(filament_color_type.empty() ? "1" : filament_color_type);
if (filament_multi_color.size() == 0)
filament_multi_color.push_back(append_color);
ams_multi_color_filment.push_back(filament_multi_color);
continue;
}
ams_filament_presets.push_back("");
ams_filament_colors.push_back("");
ams_filament_color_types.push_back("");
ams_multi_color_filment.push_back({});
} else {
ams_filament_presets.push_back("Generic PLA");//for unknow matieral
auto default_unknown_color = "#CECECE";
ams_filament_colors.push_back(default_unknown_color);
@@ -4615,12 +4728,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
filament_multi_color.push_back(default_unknown_color);
}
ams_multi_color_filment.push_back(filament_multi_color);
} else if (is_placeholder) {
// Orca: push placeholders to keep index alignment with ams_infos
ams_filament_presets.push_back("");
ams_filament_colors.push_back("");
ams_filament_color_types.push_back("");
ams_multi_color_filment.push_back({});
}
}
continue;
}
@@ -4632,49 +4740,19 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
continue;
}
bool has_type = false;
auto filament_type = ams.opt_string("filament_type", 0u);
auto iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
warn_ambiguous_filament_id_match(filaments, iter, filament_id);
if (iter == filaments.end()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
if (!filament_type.empty()) {
auto original_type = filament_type;
std::string resolved_generic;
auto generic = find_generic_preset_for_type(filaments, filament_type, resolved_generic);
if (!filament_type.empty())
filament_type = "Generic " + filament_type;
iter = std::find_if(filaments.begin(), filaments.end(), [&filament_type](auto &f) {
return f.is_compatible && f.is_system
&& boost::algorithm::starts_with(f.name, filament_type);
});
if (iter == filaments.end()) {
// Similarity fallback: find a generic preset whose filament_type
// appears as a whole word in the AMS type (e.g. "ASA" in "ASA Sparkle").
auto upper_type = boost::to_upper_copy(original_type);
auto contains_word = [](const std::string& haystack, const std::string& needle) {
auto pos = haystack.find(needle);
while (pos != std::string::npos) {
bool start_ok = (pos == 0 || !std::isalnum(static_cast<unsigned char>(haystack[pos - 1])));
bool end_ok = (pos + needle.size() >= haystack.size() ||
!std::isalnum(static_cast<unsigned char>(haystack[pos + needle.size()])));
if (start_ok && end_ok)
return true;
pos = haystack.find(needle, pos + 1);
}
return false;
};
// Find the longest-matching preset type to prefer e.g. "PA-CF" over "PA".
size_t best_len = 0;
for (auto it = filaments.begin(); it != filaments.end(); ++it) {
if (!it->is_compatible || !it->is_system || !boost::algorithm::starts_with(it->name, "Generic "))
continue;
auto preset_type = boost::to_upper_copy(it->config.opt_string("filament_type", 0u));
if (preset_type.size() > best_len && contains_word(upper_type, preset_type)) {
iter = it;
best_len = preset_type.size();
filament_type = "Generic " + it->config.opt_string("filament_type", 0u);
}
}
}
if (generic != filaments.end()) {
iter = generic;
filament_type = resolved_generic;
}
if (iter == filaments.end()) {
// Prefer old selection
@@ -4834,6 +4912,8 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
}
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": color_only mapped_any %1% maps %2% entries %3%")
% mapped_any % maps.size() % ams_infos.size();
filament_color->values = exist_colors;
ams_multi_color_filment = exist_multi_color_filment;
merge_info.merges.clear();
@@ -4893,13 +4973,18 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
}
}
for (size_t i = 0; i < ams_infos.size(); i++) {// check append
if (ams_infos[i].valid) {
// A mapped tray (valid id) and a data-bearing tray without an id both append the same
// way; only a tray with neither contributes nothing.
if (ams_infos[i].valid || ams_infos[i].has_reported_data) {
if (i >= ams_filament_presets.size()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "check error: array bound (check append)";
continue;
}
ams_infos[i].filament_preset = ams_filament_presets[i];
ams_infos[i].mutli_filament_color = ams_multi_color_filment[i];
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": entry %1% mapped %2% preset %3% color %4% valid %5% reported_data %6%")
% i % ams_infos[i].is_map % ams_infos[i].filament_preset % ams_infos[i].filament_color
% ams_infos[i].valid % ams_infos[i].has_reported_data;
if (!ams_infos[i].is_map) {
need_append_colors.emplace_back(ams_infos[i]);
ams_filament_colors[i] = "";
@@ -4909,6 +4994,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
}
}
else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": entry %1% dropped (no valid id or reported data)") % i;
ams_filament_colors[i] = "";
ams_filament_color_types[i] = "";
ams_filament_presets[i] = "";
@@ -4926,7 +5012,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
[](std::vector<std::string> &value) { return value.empty(); }),
ams_multi_color_filment.end());
if (need_append_colors.size() > 0 && enable_append) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "need_append_colors.size() > 0 && enable_append";
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": append candidates %1% (start existing %2%)") % need_append_colors.size() % exist_filament_presets.size();
auto get_idx_in_array = [](std::vector<std::string> &presets, std::vector<std::string> &colors, const std::string &preset, const std::string &color) -> int {
for (size_t i = 0; i < presets.size(); i++) {
if (presets[i] == preset && colors[i] == color) {
@@ -4937,17 +5023,22 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
};
for (size_t i = 0; i < need_append_colors.size(); i++){
if (exist_filament_presets.size() >= MAXIMUM_AMS_SYNC_FILAMENT_NUMBER){
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": append capped at %1%, %2% candidate(s) left") % MAXIMUM_AMS_SYNC_FILAMENT_NUMBER % (need_append_colors.size() - i);
break;
}
auto idx = get_idx_in_array(exist_filament_presets, exist_colors, need_append_colors[i].filament_preset, need_append_colors[i].filament_color);
if (idx >= 0) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": append dedup preset %1% color %2% already at %3%") % need_append_colors[i].filament_preset % need_append_colors[i].filament_color % idx;
continue;
}
exist_filament_presets.push_back(need_append_colors[i].filament_preset);
exist_colors.push_back(need_append_colors[i].filament_color);
exist_color_types.push_back(need_append_colors[i].filament_color_type);
exist_multi_color_filment.push_back(need_append_colors[i].mutli_filament_color);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": appended preset %1% color %2% type %3% -> slot %4%") % need_append_colors[i].filament_preset % need_append_colors[i].filament_color % need_append_colors[i].filament_color_type % (exist_filament_presets.size() - 1);
}
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": append skipped candidates %1% enable_append %2%") % need_append_colors.size() % enable_append;
}
filament_color->values = exist_colors;
filament_color_type->values = exist_color_types;
@@ -4984,6 +5075,10 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
result_multi_colors.push_back(
i < ams_multi_color_filment.size() ? ams_multi_color_filment[i]
: std::vector<std::string>{ams_filament_colors[i]});
} else if (i < ams_infos.size() && ams_infos[i].is_placeholder) {
// Orca: an empty AMS slot is a placeholder, not a filament, so drop it instead of
// keeping or filling a slot for it. The device AMS panel still shows the empty tray.
continue;
} else if (i < exist_presets.size()) {
// Empty tray or beyond tray count: keep existing filament
result_colors.push_back(exist_colors[i]);
@@ -5008,8 +5103,8 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
filament_color_type->values = result_color_types;
this->filament_presets = result_presets;
ams_multi_color_filment = result_multi_colors;
filament_map->values.resize(total, 1);
filament_volume_map->values.resize(total, static_cast<int>(NozzleVolumeType::nvtStandard));
filament_map->values.resize(result_presets.size(), 1);
filament_volume_map->values.resize(result_presets.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
} else {
// BBL: existing wholesale replace
filament_color->values = ams_filament_colors;
@@ -5059,7 +5154,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
// Update ams_multi_color_filment
update_filament_multi_color();
update_multi_material_filament_presets();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "finish sync ams list";
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": finish sync ams list, end_presets %1% end_colors %2%") % this->filament_presets.size() % filament_color->values.size();
return this->filament_presets.size();
}
@@ -5138,6 +5233,20 @@ std::vector<std::vector<DynamicPrintConfig>> PresetBundle::get_extruder_filament
if (extruder_nums > 1) {
filament_infos.resize(extruder_nums, std::vector<DynamicPrintConfig>());
for (auto ams_item : filament_ams_list) {
// A generic slot carries the extruder it actually belongs to, so it lands in that
// extruder's bucket instead of the legacy left/right pair. Bambu entries (and anything
// without the field) keep the existing 0x10000 key convention.
const bool has_extruder_id = ams_item.second.has("device_tray_extruder_id");
const int entry_extruder = has_extruder_id ? ams_item.second.opt_int("device_tray_extruder_id") : -1;
if (has_extruder_id) {
if (entry_extruder >= 0 && entry_extruder < extruder_nums) {
// Drop the transient association before the config reaches the slicing pipeline.
DynamicPrintConfig entry = ams_item.second;
entry.erase("device_tray_extruder_id");
filament_infos[entry_extruder].push_back(std::move(entry));
}
continue;
}
if (ams_item.first & 0x10000) { // right
filament_infos[1].push_back(ams_item.second);
} else { // left
+5 -4
View File
@@ -858,10 +858,11 @@ void PrintObject::contour_z()
BOOST_LOG_TRIVIAL(debug) << "Contouring in parallel - start";
TriangleMesh mesh = this->m_model_object->raw_mesh();
// A PrintObject groups the instances that share their whole transformation but the XY shift: an
// instance rotated, scaled or mirrored differently is a PrintObject of its own. So the first
// instance stands for all of them.
const ModelInstance *inst = m_instances.front().model_instance;
if (m_model_object->instances.size() != 1) {
throw RuntimeError("ContourZ: unexpected number of instances");
}
ModelInstance *inst = m_model_object->instances.front();
Point center_offset = this->center_offset();
Geometry::Transformation trans = inst->get_transformation();
-6
View File
@@ -222,7 +222,6 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoUtils.hpp
GUI/GLCanvas3D.cpp
GUI/GLCanvas3D.hpp
GUI/SequentialPrintClearance.hpp
GUI/GLModel.cpp
GUI/GLModel.hpp
GUI/GLSelectionRectangle.cpp
@@ -690,8 +689,6 @@ set(SLIC3R_GUI_SOURCES
plugin/host/PluginHostUi.hpp
plugin/host/PluginPages.cpp
plugin/host/PluginPages.hpp
plugin/host/PluginResourceTextures.cpp
plugin/host/PluginResourceTextures.hpp
plugin/CloudPluginService.cpp
plugin/CloudPluginService.hpp
plugin/PluginFsUtils.cpp
@@ -715,9 +712,6 @@ set(SLIC3R_GUI_SOURCES
plugin/pluginTypes/pages/PagesPluginCapability.hpp
plugin/pluginTypes/pages/PagesPluginCapability.cpp
plugin/pluginTypes/pages/PagesPluginCapabilityTrampoline.hpp
plugin/pluginTypes/resourceTexture/ResourceTexturePluginCapability.hpp
plugin/pluginTypes/resourceTexture/ResourceTexturePluginCapability.cpp
plugin/pluginTypes/resourceTexture/ResourceTexturePluginCapabilityTrampoline.hpp
plugin/pluginTypes/script/ScriptPluginCapability.hpp
plugin/pluginTypes/script/ScriptPluginCapability.cpp
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp
+71 -259
View File
@@ -85,96 +85,6 @@ static std::string float_to_string_with_precision(float value, int precision = 3
return stream.str();
}
static std::string colour_to_ams_string(const wxColour& color)
{
char col_buf[10];
sprintf(col_buf, "%02X%02X%02X%02X", (int)color.Red(), (int)color.Green(), (int)color.Blue(), (int)color.Alpha());
return col_buf;
}
static wxColour mix_colour(const wxColour& left, const wxColour& right, double ratio)
{
ratio = std::max(0.0, std::min(1.0, ratio));
auto mix_channel = [ratio](unsigned char l, unsigned char r) {
return static_cast<unsigned char>(std::round(l + (r - l) * ratio));
};
return wxColour(mix_channel(left.Red(), right.Red()),
mix_channel(left.Green(), right.Green()),
mix_channel(left.Blue(), right.Blue()),
mix_channel(left.Alpha(), right.Alpha()));
}
static std::vector<ColorPickerPopup::ColorItem> collect_ams_color_items(DevFilaSystem* fila_system)
{
std::vector<ColorPickerPopup::ColorItem> items;
if (!fila_system) return items;
for (const auto& ams_pair : fila_system->GetAmsList()) {
DevAms* ams = ams_pair.second;
if (!ams) continue;
for (const auto& tray_pair : ams->GetTrays()) {
DevAmsTray* tray = tray_pair.second;
if (!tray || !tray->is_tray_info_ready()) continue;
ColorPickerPopup::ColorItem item;
for (const std::string& col : tray->cols) {
item.colors.emplace_back(DevAmsTray::decode_color(col));
}
if (item.colors.empty() && !tray->color.empty()) {
item.colors.emplace_back(DevAmsTray::decode_color(tray->color));
}
if (item.colors.empty()) continue;
item.ctype = item.colors.size() > 1 ? tray->ctype : 2;
items.emplace_back(std::move(item));
}
}
return items;
}
static std::vector<ColorPickerPopup::ColorItem> collect_preset_color_items(const std::string& filament_id)
{
std::vector<ColorPickerPopup::ColorItem> items;
if (filament_id.empty()) return items;
auto* clr_query = GUI::wxGetApp().get_filament_color_code_query();
if (!clr_query) return items;
auto* agent = wxGetApp().getAgent();
const std::string catalog_id = agent ? agent->from_orca_filament_id(filament_id) : filament_id;
FilamentColorCodes* color_codes = clr_query->GetFilaInfoMap(wxString::FromUTF8(catalog_id));
if (!color_codes || !color_codes->GetFilamentColor2CodeMap()) return items;
std::set<std::string> seen;
for (const auto& color_pair : *color_codes->GetFilamentColor2CodeMap()) {
const FilamentColor& fila_color = color_pair.first;
FilamentColorCode* color_code = color_pair.second;
if (!color_code || fila_color.GetColors().empty()) continue;
ColorPickerPopup::ColorItem item;
item.ctype = fila_color.ColorCount() < 2 ? 2 :
(fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR ? 0 : 1);
item.name = color_code->GetFilaColorName();
std::string key = std::to_string(item.ctype);
for (const wxColour& color : fila_color.GetColors()) {
item.colors.emplace_back(color);
char rgba[9];
std::snprintf(rgba, sizeof(rgba), "%02X%02X%02X%02X",
static_cast<unsigned>(color.Red()), static_cast<unsigned>(color.Green()),
static_cast<unsigned>(color.Blue()), static_cast<unsigned>(color.Alpha()));
key += "|" + std::string(rgba);
}
if (item.colors.empty() || !seen.insert(key).second) continue;
items.emplace_back(std::move(item));
}
return items;
}
AMSMaterialsSetting::AMSMaterialsSetting(wxWindow *parent, wxWindowID id)
: DPIDialog(parent, id, _L("AMS Materials Setting"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
, m_color_picker_popup(ColorPickerPopup(this))
@@ -830,19 +740,8 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
nozzle_temp_min.ToLong(&nozzle_temp_min_int);
nozzle_temp_max.ToLong(&nozzle_temp_max_int);
wxColour color = m_clr_picker->m_colour;
std::string tray_color = colour_to_ams_string(color);
std::vector<std::string> tray_colors;
int tray_ctype = 2;
// Only firmware advertising fun2[23] receives the manual multi-color fields.
if (obj->is_support_filament_manual_multi_color) {
for (const wxColour& selected_color : m_clr_picker->m_cols) {
tray_colors.emplace_back(colour_to_ams_string(selected_color));
}
if (tray_colors.empty()) {
tray_colors.emplace_back(tray_color);
}
tray_ctype = tray_colors.size() > 1 ? m_clr_picker->ctype : 2;
}
char col_buf[10];
sprintf(col_buf, "%02X%02X%02X%02X", (int)color.Red(), (int)color.Green(), (int)color.Blue(), (int)color.Alpha());
if (ams_filament_id.empty() || nozzle_temp_min.empty() || nozzle_temp_max.empty() || m_filament_type.empty()) {
BOOST_LOG_TRIVIAL(trace) << "Invalid Setting id";
@@ -861,33 +760,13 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
<< ", tray_info_idx (filament_id) = " << ams_filament_id
<< ", setting_id = " << ams_setting_id
<< ", tray_type = " << m_filament_type
<< ", tray_color = " << tray_color
<< ", tray_color = " << col_buf
<< ", nozzle_temp_min = " << nozzle_temp_min_int
<< ", nozzle_temp_max = " << nozzle_temp_max_int;
// set filament
if (m_is_third) {
obj->command_ams_filament_settings(ams_id, slot_id, ams_filament_id, ams_setting_id, tray_color, m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int,
tray_colors, tray_ctype);
// Refresh the edited tray immediately while its MQTT acknowledgement is pending.
DevAmsTray* tray = nullptr;
if (is_virtual_tray()) {
for (auto& virtual_tray : obj->vt_slot) {
if (virtual_tray.id == std::to_string(ams_id)) {
tray = &virtual_tray;
break;
}
}
} else {
tray = obj->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
}
if (tray) {
tray->color = tray_color;
tray->cols = tray_colors;
tray->ctype = tray_ctype;
tray->set_hold_count();
}
obj->command_ams_filament_settings(ams_id, slot_id, ams_filament_id, ams_setting_id, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int);
}
//reset param
@@ -1044,15 +923,8 @@ void AMSMaterialsSetting::set_ctype(int ctype)
void AMSMaterialsSetting::on_picker_color(wxCommandEvent& event)
{
std::vector<wxColour> colors = m_color_picker_popup.get_selected_colours();
if (colors.empty()) {
unsigned int color_num = event.GetInt();
colors.emplace_back(color_num >> 24 & 0xFF, color_num >> 16 & 0xFF, color_num >> 8 & 0xFF, color_num & 0xFF);
}
set_ctype(m_color_picker_popup.get_selected_ctype());
set_color(colors.front());
set_colors(colors);
set_color(wxColour(color_num>>24&0xFF, color_num>>16&0xFF, color_num>>8&0xFF, color_num&0xFF));
}
void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
@@ -1067,12 +939,14 @@ void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
}
}
m_color_picker_popup.set_ams_colours(collect_ams_color_items(obj->GetFilaSystem().get()));
m_color_picker_popup.set_preset_colours(collect_preset_color_items(ams_filament_id));
m_color_picker_popup.set_def_colour(m_clr_picker->m_colour, m_clr_picker->m_cols, m_clr_picker->ctype);
std::vector<wxColour> ams_colors;
obj->GetFilaSystem()->CollectAmsColors(ams_colors);
wxPoint img_pos = m_clr_picker->ClientToScreen(wxPoint(0, 0));
wxPoint popup_pos(img_pos.x - m_color_picker_popup.GetSize().x - FromDIP(95), img_pos.y - FromDIP(65));
m_color_picker_popup.Position(popup_pos, wxSize(0, 0));
m_color_picker_popup.set_ams_colours(ams_colors);
m_color_picker_popup.set_def_colour(m_clr_picker->m_colour);
m_color_picker_popup.Popup();
}
@@ -1861,46 +1735,7 @@ void ColorPicker::doRender(wxDC& dc)
auto radius = m_show_full ? size.x / 2 - FromDIP(1) : size.x / 2;
if (m_selected) radius -= FromDIP(1);
if (m_show_full && m_cols.size() > 1) {
if (ctype == 0) {
const double center_x = size.x / 2.0;
const double center_y = size.y / 2.0;
const double draw_radius = radius;
dc.SetPen(*wxTRANSPARENT_PEN);
for (int x = 0; x < size.x; ++x) {
const double dx = x + 0.5 - center_x;
if (std::abs(dx) > draw_radius) continue;
const double half_height = std::sqrt(std::max(0.0, draw_radius * draw_radius - dx * dx));
const int top = std::max(0, static_cast<int>(std::ceil(center_y - half_height)));
const int bottom = std::min(size.y - 1, static_cast<int>(std::floor(center_y + half_height)));
const double pos = size.x > 1 ? static_cast<double>(x) / (size.x - 1) : 0.0;
const double scaled = pos * (m_cols.size() - 1);
const size_t idx = std::min(static_cast<size_t>(scaled), m_cols.size() - 2);
const double ratio = scaled - idx;
if (top <= bottom) {
dc.SetBrush(wxBrush(mix_colour(m_cols[idx], m_cols[idx + 1], ratio)));
dc.DrawRectangle(x, top, 1, bottom - top + 1);
}
}
}
else {
float ev_angle = 360.0 / m_cols.size();
const float overlap = 2.0f;
wxPoint center(size.x / 2, size.y / 2);
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxBrush(m_cols.front()));
dc.DrawCircle(center.x, center.y, radius);
for (int i = 0; i < m_cols.size(); i++) {
dc.SetBrush(m_cols[i]);
float startAngle = 270.0f + i * ev_angle;
float endAngle = startAngle + ev_angle;
dc.DrawEllipticArc(center.x - radius, center.y - radius, 2 * radius, 2 * radius, startAngle - overlap, endAngle + overlap);
}
}
}
else if (alpha == 0) {
if (alpha == 0) {
wxSize bmp_size = m_bitmap_transparent_def.GetBmpSize();
int center_x = (size.x - bmp_size.x) / 2;
int center_y = (size.y - bmp_size.y) / 2;
@@ -1942,6 +1777,52 @@ void ColorPicker::doRender(wxDC& dc)
dc.SetPen(wxPen(wxColour("#6B6B6B")));
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawCircle(size.x / 2, size.y / 2, radius);
if (m_cols.size() > 1) {
if (ctype == 0) {
int left = FromDIP(0);
float total_width = size.x;
int gwidth = std::round(total_width / (m_cols.size() - 1));
for (int i = 0; i < m_cols.size() - 1; i++) {
if ((left + gwidth) > (size.x)) {
gwidth = size.x - left;
}
auto rect = wxRect(left, 0, gwidth, size.y);
dc.GradientFillLinear(rect, m_cols[i], m_cols[i + 1], wxEAST);
left += gwidth;
}
if (wxGetApp().dark_mode()) {
dc.DrawBitmap(m_bitmap_border_dark, wxPoint(0, 0));
}
else {
dc.DrawBitmap(m_bitmap_border, wxPoint(0, 0));
}
}
else {
float ev_angle = 360.0 / m_cols.size();
float startAngle = 270.0;
float endAngle = 270.0;
dc.SetPen(*wxTRANSPARENT_PEN);
for (int i = 0; i < m_cols.size(); i++) {
dc.SetBrush(m_cols[i]);
endAngle += ev_angle;
endAngle = endAngle > 360.0 ? endAngle - 360.0 : endAngle;
wxPoint center(size.x / 2, size.y / 2);
dc.DrawEllipticArc(center.x - radius, center.y - radius, 2 * radius, 2 * radius, startAngle, endAngle);
startAngle += ev_angle;
startAngle = startAngle > 360.0 ? startAngle - 360.0 : startAngle;
}
if (wxGetApp().dark_mode()) {
dc.DrawBitmap(m_bitmap_border_dark, wxPoint(0, 0));
}
else {
dc.DrawBitmap(m_bitmap_border, wxPoint(0, 0));
}
}
}
}
if (m_is_empty) {
@@ -2010,21 +1891,19 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
m_ams_fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
//other
m_other_fg_sizer = new wxFlexGridSizer(0, 8, 0, 0);
m_other_fg_sizer->SetFlexibleDirection(wxBOTH);
m_other_fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
wxFlexGridSizer* fg_sizer;
fg_sizer = new wxFlexGridSizer(0, 8, 0, 0);
fg_sizer->SetFlexibleDirection(wxBOTH);
fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
for (wxColour col : m_def_colors) {
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
cp->set_color(col);
cp->set_colors({col});
cp->ctype = 2;
cp->set_selected(false);
cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238)));
m_color_pickers.push_back(cp);
m_default_color_pickers.push_back(cp);
m_other_fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
set_def_colour(cp->m_colour);
@@ -2091,7 +1970,7 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
m_sizer_box->Add(m_sizer_ams, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
m_sizer_box->Add(m_ams_fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
m_sizer_box->Add(m_sizer_other, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
m_sizer_box->Add(m_other_fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
m_sizer_box->Add(fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
m_sizer_box->Add(m_sizer_custom, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
m_sizer_box->Add(m_custom_cp, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(16));
m_sizer_box->Add(0, 0, 0, wxTOP, FromDIP(10));
@@ -2102,7 +1981,7 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
m_def_color_box->Fit();
m_sizer_main->Add(m_def_color_box, 0, wxALL | wxEXPAND, 10);
SetSizerAndFit(m_sizer_main);
SetSizer(m_sizer_main);
Layout();
Fit();
@@ -2140,15 +2019,13 @@ void ColorPickerPopup::on_custom_clr_picker(wxMouseEvent& /*event*/)
wxPostEvent(GetParent(), evt);
}
void ColorPickerPopup::set_ams_colours(const std::vector<ColorItem>& ams)
void ColorPickerPopup::set_ams_colours(std::vector<wxColour> ams)
{
m_ams_fg_sizer->Clear(false);
if (m_ams_color_pickers.size() > 0) {
for (ColorPicker* col_pick:m_ams_color_pickers) {
std::vector<ColorPicker*>::iterator iter = find(m_color_pickers.begin(), m_color_pickers.end(), col_pick);
if (iter != m_color_pickers.end()) {
col_pick->Hide();
col_pick->Destroy();
m_color_pickers.erase(iter);
}
@@ -2158,22 +2035,17 @@ void ColorPickerPopup::set_ams_colours(const std::vector<ColorItem>& ams)
}
m_ams_color_items = ams;
for (const ColorItem& item : m_ams_color_items) {
if (item.colors.empty()) continue;
m_ams_colors = ams;
for (wxColour col : m_ams_colors) {
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
cp->set_color(item.colors.front());
cp->set_colors(item.colors);
cp->ctype = item.colors.size() > 1 ? item.ctype : 2;
cp->set_show_full(true);
cp->set_color(col);
cp->set_selected(false);
cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238)));
m_color_pickers.push_back(cp);
m_ams_color_pickers.push_back(cp);
m_ams_fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
set_def_colour(cp->m_colour, cp->m_cols, cp->ctype);
set_def_colour(cp->m_colour);
wxCommandEvent evt(EVT_SELECTED_COLOR);
unsigned long g_col = ((cp->m_colour.Red() & 0xff) << 24) + ((cp->m_colour.Green() & 0xff) << 16) + ((cp->m_colour.Blue() & 0xff) << 8) + (cp->m_colour.Alpha() & 0xff);
@@ -2186,68 +2058,9 @@ void ColorPickerPopup::set_ams_colours(const std::vector<ColorItem>& ams)
Fit();
}
void ColorPickerPopup::set_preset_colours(const std::vector<ColorItem>& preset_colors)
{
m_other_fg_sizer->Clear(false);
if (!m_preset_color_pickers.empty()) {
for (ColorPicker* col_pick : m_preset_color_pickers) {
auto iter = std::find(m_color_pickers.begin(), m_color_pickers.end(), col_pick);
if (iter != m_color_pickers.end()) {
col_pick->Hide();
col_pick->Destroy();
m_color_pickers.erase(iter);
}
}
m_preset_color_pickers.clear();
}
for (const ColorItem& item : preset_colors) {
if (item.colors.empty()) continue;
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
cp->set_color(item.colors.front());
cp->set_colors(item.colors);
cp->ctype = item.colors.size() > 1 ? item.ctype : 2;
cp->set_show_full(true);
cp->set_selected(false);
cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238)));
if (!item.name.empty()) {
cp->SetToolTip(item.name);
}
m_color_pickers.push_back(cp);
m_preset_color_pickers.push_back(cp);
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
set_def_colour(cp->m_colour, cp->m_cols, cp->ctype);
wxCommandEvent evt(EVT_SELECTED_COLOR);
unsigned long g_col = ((cp->m_colour.Red() & 0xff) << 24) + ((cp->m_colour.Green() & 0xff) << 16) + ((cp->m_colour.Blue() & 0xff) << 8) + (cp->m_colour.Alpha() & 0xff);
evt.SetInt(g_col);
wxPostEvent(GetParent(), evt);
});
}
const bool show_default_colours = m_preset_color_pickers.empty();
for (ColorPicker* cp : m_default_color_pickers) {
cp->Show(show_default_colours);
}
for (ColorPicker* cp : (show_default_colours ? m_default_color_pickers : m_preset_color_pickers)) {
m_other_fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
}
m_other_fg_sizer->Layout();
Layout();
Fit();
}
void ColorPickerPopup::set_def_colour(wxColour col, std::vector<wxColour> cols, int ctype)
void ColorPickerPopup::set_def_colour(wxColour col)
{
m_def_col = col;
if (cols.empty()) {
cols.push_back(col);
}
m_def_cols = cols;
m_def_ctype = m_def_cols.size() > 1 ? ctype : 2;
for (ColorPicker* cp : m_color_pickers) {
if (cp->m_selected) {
@@ -2256,8 +2069,7 @@ void ColorPickerPopup::set_def_colour(wxColour col, std::vector<wxColour> cols,
}
for (ColorPicker* cp : m_color_pickers) {
if (!cp->IsShown()) continue;
if (cp->ctype == m_def_ctype && cp->m_cols == m_def_cols) {
if (cp->m_colour == m_def_col) {
cp->set_selected(true);
break;
}
+4 -19
View File
@@ -82,22 +82,14 @@ public:
class ColorPickerPopup : public PopupWindow
{
public:
struct ColorItem
{
std::vector<wxColour> colors;
int ctype = 2;
wxString name;
};
ScalableBitmap m_ts_bitmap_custom;
wxStaticBitmap* m_ts_stbitmap_custom;
StaticBox* m_custom_cp;
StaticBox* m_def_color_box;
wxFlexGridSizer* m_ams_fg_sizer;
wxFlexGridSizer* m_other_fg_sizer;
wxColour m_def_col;
std::vector<wxColour> m_def_colors;
std::vector<ColorItem> m_ams_color_items;
std::vector<wxColour> m_ams_colors;
std::vector<ColorPicker*> m_color_pickers;
std::vector<ColorPicker*> m_ams_color_pickers;
@@ -105,20 +97,13 @@ public:
ColorPickerPopup(wxWindow* parent);
~ColorPickerPopup() {};
void on_custom_clr_picker(wxMouseEvent& event);
void set_ams_colours(const std::vector<ColorItem>& ams);
void set_preset_colours(const std::vector<ColorItem>& preset_colors);
void set_def_colour(wxColour col, std::vector<wxColour> cols = {}, int ctype = 2);
const std::vector<wxColour>& get_selected_colours() const { return m_def_cols; }
int get_selected_ctype() const { return m_def_ctype; }
void set_ams_colours(std::vector<wxColour> ams);
void set_def_colour(wxColour col);
void paintEvent(wxPaintEvent& evt);
virtual void OnDismiss() wxOVERRIDE;
virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE;
private:
std::vector<ColorPicker*> m_default_color_pickers;
std::vector<ColorPicker*> m_preset_color_pickers;
std::vector<wxColour> m_def_cols;
int m_def_ctype = 2;
public:
};
+1 -3
View File
@@ -747,9 +747,7 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model& model, const Dyn
// Orca: prevent resetting under gcode viewer mode
if (invalidated != PrintBase::APPLY_STATUS_UNCHANGED) {
// Use the app’s plater accessor instead of going through mainframe, otherwise anything during startup that reaches apply()
// (loading presets, config or input files, e.g. from the CLI) therefore dereferences a null `mainframe` and crashes.
const auto plater = GUI::wxGetApp().plater();
const auto plater = GUI::wxGetApp().mainframe->m_plater;
if (plater && plater->only_gcode_mode()) {
invalidated = PrintBase::APPLY_STATUS_UNCHANGED;
}
+5 -2
View File
@@ -163,8 +163,8 @@ public:
~devPrinterUtil() = delete;
public:
static bool IsVirtualSlot(int ams_id) { return (ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID);}
static bool IsVirtualSlot(const std::string& ams_id) { return (ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR); }
static bool IsVirtualSlot(int ams_id);
static bool IsVirtualSlot(const std::string& ams_id);
};
namespace GUI
@@ -198,3 +198,6 @@ template<> struct std::hash<NozzleDef>
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
};
};
// key(extruder_id) -> { key1(nozzle type info), val1( number of the nozzle type)}
using ExtruderNozzleInfos = std::unordered_map<int, std::unordered_map<NozzleDef, int>>;
+151 -14
View File
@@ -8,6 +8,7 @@
#include <cstdlib>
#include <chrono>
#include <nlohmann/json.hpp>
#include <wx/app.h>
#include <wx/colour.h>
#include <string>
#include <wx/string.h>
@@ -33,6 +34,36 @@
using namespace nlohmann;
namespace Slic3r {
bool devPrinterUtil::IsVirtualSlot(int ams_id)
{
// When the app or preset bundle is unavailable we cannot know the vendor's
// extruder count, so fall back deterministically to the fixed Bambu pair
// {255, 254} rather than guessing from an unknown generic layout.
if (wxTheApp == nullptr || GUI::wxGetApp().preset_bundle == nullptr || GUI::wxGetApp().preset_bundle->is_bbl_vendor())
return ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID;
const int extruder_count = GUI::wxGetApp().preset_bundle->get_printer_extruder_count();
return ams_id >= VIRTUAL_TRAY_MAIN_ID - extruder_count + 1 && ams_id <= VIRTUAL_TRAY_MAIN_ID;
}
bool devPrinterUtil::IsVirtualSlot(const std::string& ams_id)
{
// Strict: only a fully numeric string is a valid slot id. std::stoi alone
// would accept trailing garbage (e.g. "255abc" -> 255).
if (ams_id.empty())
return false;
for (char c : ams_id) {
if (c < '0' || c > '9')
return false;
}
try {
return IsVirtualSlot(std::stoi(ams_id));
} catch (...) {
return false;
}
}
static int _hex_digit_to_int(const char c) { return (c >= '0' && c <= '9') ? c - '0' : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : -1; }
wxColour DevAmsTray::decode_color(const std::string &color)
@@ -200,18 +231,22 @@ wxString DevAms::GetDisplayName() const
int DevAms::GetSlotCount() const
{
// N3S is a 1-slot Bambu contract, so report 1 regardless of whether the
// preset bundle is momentarily unavailable (m_trays.size() can be 0).
if (GetAmsType() == N3S)
return 1;
if (wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr &&
GUI::wxGetApp().preset_bundle->is_bbl_vendor()) {
// GetAmsType() maps AMS_LITE_MIXED -> AMS_LITE, so N9 reports 4 slots like AMS-Lite.
auto ams_type = GetAmsType();
if (ams_type == AMS || ams_type == AMS_LITE || ams_type == N3F)
{
return 4;
}
else if (ams_type == N3S)
{
return 1;
}
return 1;
return static_cast<int>(m_trays.size());
}
DevAmsTray* DevAms::GetTray(const std::string& tray_id) const
@@ -302,13 +337,21 @@ int DevFilaSystem::GetExtruderIdByAmsId(const std::string& ams_id) const
{
return it->second->GetExtruderId();
}
else if (stoi(ams_id) == VIRTUAL_TRAY_MAIN_ID)
{
return MAIN_EXTRUDER_ID;
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor();
if (!is_bbl_vendor && GetOwner()) {
for (const auto& tray : GetOwner()->vt_slot) {
if (tray.id == ams_id)
return VIRTUAL_TRAY_MAIN_ID - std::stoi(ams_id);
}
else if (stoi(ams_id) == VIRTUAL_TRAY_DEPUTY_ID)
{
}
if (is_bbl_vendor && ams_id == VIRTUAL_AMS_MAIN_ID_STR)
return MAIN_EXTRUDER_ID;
if (is_bbl_vendor && ams_id == VIRTUAL_AMS_DEPUTY_ID_STR)
return DEPUTY_EXTRUDER_ID;
if (!is_bbl_vendor && devPrinterUtil::IsVirtualSlot(ams_id)) {
return VIRTUAL_TRAY_MAIN_ID - std::stoi(ams_id);
}
assert(false && __FUNCTION__);
@@ -325,9 +368,22 @@ std::string DevFilaSystem::GetNozzleFlowStringByAmsId(const std::string& ams_id)
std::map<int, DevAmsSlotId> DevFilaSystem::GetTrayIndexMap()
{
std::map<int, DevAmsSlotId> tray_id_map;
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor();
if (is_bbl_vendor) {
tray_id_map[VIRTUAL_TRAY_MAIN_ID] = DevAmsSlotId{VIRTUAL_TRAY_MAIN_ID, 0};
tray_id_map[VIRTUAL_TRAY_DEPUTY_ID] = DevAmsSlotId{VIRTUAL_TRAY_DEPUTY_ID, 0};
} else if (GetOwner()) {
for (const auto& tray : GetOwner()->vt_slot) {
try {
const int tray_id = std::stoi(tray.id);
tray_id_map[tray_id] = DevAmsSlotId{tray_id, 0};
} catch (...) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " invalid virtual tray id: " << tray.id;
}
}
}
int generic_tray_index = 0;
for (auto& [ams_id, ams_item] : GetAmsList()) {
for (auto &[slot_id, slot_item] : ams_item->GetTrays()) {
if (ams_item && slot_item) {
@@ -335,7 +391,9 @@ std::map<int, DevAmsSlotId> DevFilaSystem::GetTrayIndexMap()
int ams_id_int = stoi(ams_id);
int slot_id_int = stoi(slot_id);
int tray_index = -1;
if (ams_item->GetAmsType() == DevAms::N3S) {
if (!is_bbl_vendor) {
tray_index = generic_tray_index + slot_id_int;
} else if (ams_item->GetAmsType() == DevAms::N3S) {
tray_index = ams_id_int;
} else if (ams_item->GetAmsType() == DevAms::AMS_LITE && ams_item->IsAmsLiteMixed()) {
tray_index = 24 + slot_id_int;
@@ -348,6 +406,8 @@ std::map<int, DevAmsSlotId> DevFilaSystem::GetTrayIndexMap()
}
}
}
if (!is_bbl_vendor)
generic_tray_index += static_cast<int>(ams_item->GetTrays().size());
}
return tray_id_map;
@@ -456,6 +516,13 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
ams_id_set.insert(it->first);
}
// Bambu trays index their exist bit as ams_id*4+tray_id; a generic (non-Bambu) unit
// reports its own lane count, so its trays carry a cumulative lane index instead.
// Same index DevFilaSystem::GetTrayIndexMap() builds and the agent sets bits with.
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr &&
GUI::wxGetApp().preset_bundle->is_bbl_vendor();
int generic_tray_base = 0;
for (auto it = j_ams.begin(); it != j_ams.end(); it++)
{
if (!it->contains("id")) continue;
@@ -794,9 +861,20 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
tray_id_int = atoi(curr_tray->id.c_str());
if (type_id < 4)
{
if (is_bbl_vendor)
{
curr_tray->is_exists = (obj->tray_exist_bits & (1 << (ams_id_int * 4 + tray_id_int))) != 0 ? true : false;
}
else
{
// A generic unit is not four slots wide, so its trays carry a
// cumulative lane index across units instead of ams_id*4+tray_id.
const int tray_bit = generic_tray_base + tray_id_int;
curr_tray->is_exists = tray_bit >= 0 && tray_bit < 32 &&
(obj->tray_exist_bits & (1UL << tray_bit)) != 0;
}
}
else if (type_id == DevAms::AMS_LITE_MIXED)
{
// Mixed AMS-Lite (A2L / N9) trays occupy tray-exist bits 24..27.
@@ -851,6 +929,9 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
}
}
}
if (!is_bbl_vendor && it->contains("tray") && (*it)["tray"].is_array()) {
generic_tray_base += static_cast<int>((*it)["tray"].size());
}
}
// remove not in amsList
for (auto it = ams_id_set.begin(); it != ams_id_set.end(); it++)
@@ -866,6 +947,21 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
}
}
}
// Give every live device extruder an external virtual tray slot (non-Bambu) so a plugin agent
// that reaches the model only through this parser also gets the shared external-slot topology.
if (obj)
obj->ensure_external_virtual_tray_slots();
// Diagnostic: summarize the AMS state this production parse produced, so the agent boundary
// (submit_ams_payload) box/tray counts can be correlated with what actually landed in the model.
if (obj && system) {
size_t parsed_trays = 0;
for (const auto& kv : system->amsList)
parsed_trays += kv.second->GetTrays().size();
BOOST_LOG_TRIVIAL(info) << "DevFilaSystemParser::ParseV1_0: parsed units " << system->amsList.size()
<< " trays " << parsed_trays;
}
}
static DevAms::AmsType ams_type_from_string(const std::string& s)
@@ -876,6 +972,23 @@ static DevAms::AmsType ams_type_from_string(const std::string& s)
return DevAms::AMS; // default
}
void grow_virtual_tray_slots(std::vector<DevAmsTray>& slots, std::size_t tray_id)
{
// Address a slot by its virtual tray id, never by a raw vector index: the slot for `tray_id`
// lives at index VIRTUAL_TRAY_MAIN_ID - tray_id and placeholder entries carry
// id = VIRTUAL_TRAY_MAIN_ID - index. Append-only, so a slot that already holds reported
// metadata keeps it and a sparse/out-of-order report cannot shift the id <-> extruder mapping.
if (tray_id > static_cast<std::size_t>(VIRTUAL_TRAY_MAIN_ID))
return;
const std::size_t slot_index = static_cast<std::size_t>(VIRTUAL_TRAY_MAIN_ID) - tray_id;
while (slots.size() <= slot_index) {
slots.emplace_back(std::to_string(VIRTUAL_TRAY_MAIN_ID - static_cast<int>(slots.size())));
// A filler only makes the slot addressable; it is topology, not a reported spool. Callers
// overwrite the target entry with reported metadata, which clears this marker.
slots.back().is_slot_placeholder = true;
}
}
void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* obj, DevFilaSystem* system)
{
if (!system || !data.is_object())
@@ -913,6 +1026,8 @@ void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* ob
}
ams->m_exist = true;
ams->m_binded_extruder_set = {ext_id};
ams->m_binded_switcher_pos.reset();
ams->m_current_temperature = u.value("temperature", (float) INVALID_AMS_TEMPERATURE);
ams->m_humidity_percent = u.value("humidity_percent", -1);
ams->m_left_dry_time = u.value("dry_time_min", 0);
@@ -995,16 +1110,21 @@ void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* ob
}
// --- external / direct spools -> obj->vt_slot ---
// extruder 0 -> main virtual slot, extruder >0 -> deputy.
// Keep one virtual slot per extruder. The legacy two-extruder mapping uses
// 255 for extruder 0 and 254 for extruder 1; continue that sequence for
// additional extruders (253, 252, ...).
if (obj && data.contains("external") && data["external"].is_array())
{
obj->vt_slot.clear();
std::map<int, DevAmsTray> external_slots;
for (const auto& e : data["external"])
{
if (!e.is_object())
continue;
const int ext = e.value("extruder", MAIN_EXTRUDER_ID);
const int vt_id = (ext == MAIN_EXTRUDER_ID) ? VIRTUAL_TRAY_MAIN_ID : VIRTUAL_TRAY_DEPUTY_ID;
if (ext < MAIN_EXTRUDER_ID || ext >= VIRTUAL_TRAY_MAIN_ID)
continue;
const int vt_id = VIRTUAL_TRAY_MAIN_ID - ext;
DevAmsTray tray(std::to_string(vt_id));
tray.is_exists = e.value("loaded", false);
tray.m_fila_type = e.value("material", std::string());
@@ -1013,9 +1133,26 @@ void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* ob
tray.nozzle_temp_min = std::to_string(e.value("nozzle_temp_min", 0));
tray.nozzle_temp_max = std::to_string(e.value("nozzle_temp_max", 0));
tray.remain = e.value("remain_percent", -1);
obj->vt_slot.push_back(tray);
external_slots.insert_or_assign(ext, std::move(tray));
}
// Place each reported spool at its own index (VIRTUAL_TRAY_MAIN_ID - extruder_id) instead of
// appending in report order: a sparse or out-of-order report must not compact the vector and
// shift the slot -> extruder association.
obj->vt_slot.clear();
for (const auto& entry : external_slots)
{
// entry.first is the extruder id; its virtual tray id is VIRTUAL_TRAY_MAIN_ID - extruder_id
// and the slot lives at vector index extruder_id (the id <-> index mapping grow applies).
grow_virtual_tray_slots(obj->vt_slot, static_cast<std::size_t>(VIRTUAL_TRAY_MAIN_ID - entry.first));
obj->vt_slot[entry.first] = entry.second;
}
}
// Fill any extruder still missing a slot so the model always has one external slot per live
// extruder, even when the agent reported no external data at all. Preserves reported metadata.
if (obj)
obj->ensure_external_virtual_tray_slots();
}
}
+13 -2
View File
@@ -7,6 +7,7 @@
#include "DevFilaSwitch.h"
#include "DevUtil.h"
#include <cstddef>
#include <map>
#include <set>
#include <string>
@@ -82,7 +83,7 @@ public:
wxColour wx_color;
bool is_bbl;
bool is_exists = false;
bool is_slot_placeholder = false; // Orca: True for empty tray slots from pull-mode agents
bool is_slot_placeholder = false; // Orca: True for synthetic topology slots or empty reported trays
int hold_count = 0;
int remain = 0; // filament remain: 0 ~ 100
@@ -114,6 +115,16 @@ public:
std::optional<DevFilamentDryingPreset> get_ams_drying_preset() const;
};
/**
* Ensure the virtual-tray vector can address the slot for a virtual tray id. The slot's index is
* `VIRTUAL_TRAY_MAIN_ID - tray_id` and placeholder entries carry id = VIRTUAL_TRAY_MAIN_ID - index.
* Existing entries are left untouched, so a slot already populated with reported filament data
* keeps it; callers then assign the reported tray at index `VIRTUAL_TRAY_MAIN_ID - tray_id`.
* Appended placeholders are marked `is_slot_placeholder`, so consumers can tell a synthetic
* topology filler from a spool the printer actually reported (overwriting the entry clears it).
*/
void grow_virtual_tray_slots(std::vector<DevAmsTray>& slots, std::size_t tray_id);
/**
* DevAms - Represents a single AMS (Automatic Material System) unit.
*
@@ -348,7 +359,7 @@ public:
DevAmsTray* GetAmsTray(const std::string& ams_id, const std::string& tray_id) const;
void CollectAmsColors(std::vector<wxColour>& ams_colors) const;
// Map a linear tray index -> {ams_id, slot_id}. Includes the two virtual (external-spool) trays,
// Map a linear tray index -> {ams_id, slot_id}. Includes virtual external-spool trays,
// N3S single-slot units, and the A2L/N9 AMS-Lite-mixed layout (trays 24-27).
std::map<int, DevAmsSlotId> GetTrayIndexMap();
+162 -12
View File
@@ -9,6 +9,7 @@
#include <vector>
#include <string>
#include <utility>
#include <wx/app.h>
#include <wx/colour.h>
#include <set>
#include "slic3r/GUI/DeviceCore/DevDefs.h"
@@ -18,14 +19,80 @@
// TODO: remove this include
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GuiColor.hpp"
namespace Slic3r
{
// Bambu keeps its fixed external pair (255 main/right, 254 deputy/left) and its two
// left/right flags. Every other printer addresses one external virtual tray per live
// extruder, so ids 255, 254, 253, ... are extruders 0, 1, 2, ... .
static bool _is_bbl_vendor_printer()
{
return wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr &&
GUI::wxGetApp().preset_bundle->is_bbl_vendor();
}
// Extruder a virtual (external spool) tray id feeds: VIRTUAL_TRAY_MAIN_ID - tray_id.
// Returns -1 for an empty, non-numeric or out-of-range id.
static int _virtual_tray_extruder_id(const std::string& tray_id)
{
if (tray_id.empty())
return -1;
for (char c : tray_id)
{
if (c < '0' || c > '9')
return -1;
}
try
{
const int vt_id = std::stoi(tray_id);
if (vt_id < 0 || vt_id > VIRTUAL_TRAY_MAIN_ID)
return -1;
return VIRTUAL_TRAY_MAIN_ID - vt_id;
}
catch (...)
{
return -1;
}
}
// True when obj->vt_slot actually carries this virtual tray id as a reported spool. A synthetic
// topology filler (is_slot_placeholder) only makes the slot addressable and does not count.
static bool _has_virtual_tray_slot(const MachineObject* obj, const std::string& tray_id)
{
if (obj == nullptr || tray_id.empty())
return false;
for (const auto& tray : obj->vt_slot)
{
if (!tray.is_slot_placeholder && tray.id == tray_id)
return true;
}
return false;
}
// A generic printer may expose one AMS unit / external slot per extruder. When both legacy
// flags of a category are enabled (the sidebar sync call uses both AMS and both external flags)
// every extruder participates; when only one side is enabled (SelectMachine's left/right split)
// keep to that side's legacy extruder (right = main = 0, left = deputy = 1), so the N slots do
// not leak across sides.
static bool _generic_extruder_in_selected_side(int extruder_id, bool use_right, bool use_left)
{
if (use_right && use_left)
return true;
if (use_right)
return extruder_id == MAIN_EXTRUDER_ID;
if (use_left)
return extruder_id == DEPUTY_EXTRUDER_ID;
return false;
}
bool DevMappingUtil::is_valid_mapping_result(const MachineObject* obj, std::vector<FilamentInfo>& result, bool check_empty_slot)
{
if (result.empty()) return false;
const bool is_bbl_vendor = _is_bbl_vendor_printer();
for (int i = 0; i < result.size(); i++)
{
// invalid mapping result
@@ -42,8 +109,13 @@ namespace Slic3r
auto ams_item = obj->GetFilaSystem()->GetAmsById(result[i].ams_id);
if (ams_item == nullptr)
{
if ((result[i].ams_id != std::to_string(VIRTUAL_TRAY_MAIN_ID)) &&
(result[i].ams_id != std::to_string(VIRTUAL_TRAY_DEPUTY_ID)))
// A virtual (external spool) tray is not an AMS unit. Bambu keeps its fixed pair;
// a generic printer accepts whichever per-extruder slots it actually reported.
const bool is_virtual_tray = is_bbl_vendor
? (result[i].ams_id == std::to_string(VIRTUAL_TRAY_MAIN_ID) ||
result[i].ams_id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID))
: _has_virtual_tray_slot(obj, result[i].ams_id);
if (!is_virtual_tray)
{
result[i].tray_id = -1;
return false;
@@ -94,8 +166,11 @@ namespace Slic3r
result.ams_id = std::to_string(ams_id);
result.slot_id = std::to_string(slot_id);
if (ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID)
if (type == DevAms::EXT_SPOOL || ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID)
{
// An external spool is addressed by its virtual tray id (255 -> extruder 0, 254 -> 1,
// ...); the id is the tray's identity, so it must not fall through to the AMS
// ams_id * 4 + slot_id indexing.
result.tray_id = atoi(tray.id.c_str());
result.id = atoi(tray.id.c_str());
}
@@ -126,11 +201,25 @@ namespace Slic3r
std::map<int, FilamentInfo> tray_filaments; // tray_index : tray_color
bool left_nozzle_has_ams = false, right_nozzle_has_ams = false;
const bool is_bbl_vendor = _is_bbl_vendor_printer();
// Extruders whose AMS filament participates in this mapping. On a generic printer this
// carries the per-extruder "nozzle already has AMS" state that the legacy right/left flags
// express for Bambu, so the matching external spool can be suppressed.
std::set<int> generic_extruders_with_ams;
const auto& ams_list = obj->GetFilaSystem()->GetAmsList();
for (auto ams = ams_list.begin(); ams != ams_list.end(); ams++)
{
std::string ams_id = ams->second->GetAmsId();
auto ams_type = ams->second->GetAmsType();
const int ams_id_int = atoi(ams->first.c_str());
int generic_tray_base = 0;
if (!is_bbl_vendor) {
for (const auto& other : ams_list) {
if (atoi(other.first.c_str()) < ams_id_int)
generic_tray_base += static_cast<int>(other.second->GetTrays().size());
}
}
// GetAmsType() maps mixed -> AMS_LITE; recover the mixed type so N9 trays index at 24+slot.
if (ams_type == DevAms::AMS_LITE && ams->second->IsAmsLiteMixed())
{
@@ -138,10 +227,16 @@ namespace Slic3r
}
for (auto tray = ams->second->GetTrays().begin(); tray != ams->second->GetTrays().end(); tray++)
{
int ams_id = atoi(ams->first.c_str());
int ams_id = ams_id_int;
int tray_id = atoi(tray->first.c_str());
int tray_index = 0;
if (ams_type == DevAms::AMS || ams_type == DevAms::AMS_LITE || ams_type == DevAms::N3F)
if (!is_bbl_vendor)
{
// Generic units can have different lane counts; index all lanes cumulatively,
// matching the shared tray-index map and sidebar list builder.
tray_index = generic_tray_base + tray_id;
}
else if (ams_type == DevAms::AMS || ams_type == DevAms::AMS_LITE || ams_type == DevAms::N3F)
{
tray_index = ams_id * 4 + tray_id;
}
@@ -169,14 +264,18 @@ namespace Slic3r
if (tray->second->is_tray_info_ready())
{
_parse_tray_info(ams_id, tray_id, ams_type, *(tray->second), info);
// Generic unit lanes use one cumulative index across variable-width units;
// ams_id * 4 + slot_id is only valid for Bambu's fixed four-slot units.
if (!is_bbl_vendor)
info.id = tray_index;
}
bool ams_valid = false;
if (is_bbl_vendor)
{
//first: left,nozzle=1,map=1 second: right,nozzle=0,map=2
bool right_ams_valid = (ams->second->GetBindedExtruderSet().count(MAIN_EXTRUDER_ID) != 0) && map_opt[MappingOption::USE_RIGHT_AMS];
bool left_ams_valid = (ams->second->GetBindedExtruderSet().count(DEPUTY_EXTRUDER_ID) != 0) && map_opt[MappingOption::USE_LEFT_AMS];
if (right_ams_valid || left_ams_valid)
{
tray_filaments.emplace(std::make_pair(tray_index, info));
if (right_ams_valid)
{
right_nozzle_has_ams = true;
@@ -185,6 +284,25 @@ namespace Slic3r
{
left_nozzle_has_ams = true;
}
ams_valid = right_ams_valid || left_ams_valid;
}
else
{
// A generic unit is bound to its own extruder, so it takes part whenever the
// request covers that extruder.
for (int extruder_id : ams->second->GetBindedExtruderSet())
{
if (_generic_extruder_in_selected_side(extruder_id, map_opt[MappingOption::USE_RIGHT_AMS], map_opt[MappingOption::USE_LEFT_AMS]))
{
ams_valid = true;
generic_extruders_with_ams.insert(extruder_id);
}
}
}
if (ams_valid)
{
tray_filaments.emplace(std::make_pair(tray_index, info));
}
}
}
@@ -193,10 +311,21 @@ namespace Slic3r
{
for (auto tray : obj->vt_slot)
{
bool right_ext_valid = (tray.id == std::to_string(VIRTUAL_TRAY_MAIN_ID) && map_opt[MappingOption::USE_RIGHT_EXT]);
bool left_ext_valid = (tray.id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID) && map_opt[MappingOption::USE_LEFT_EXT]);
if (right_ext_valid || left_ext_valid)
// A synthetic topology filler is not a reported external spool, so it is never a
// mapping candidate. Bambu keeps its fixed reported pair.
if (!is_bbl_vendor && tray.is_slot_placeholder)
{
continue;
}
if (is_bbl_vendor)
{
// Bambu keeps its fixed pair and the two left/right external flags.
const bool right_ext_valid = (tray.id == std::to_string(VIRTUAL_TRAY_MAIN_ID) && map_opt[MappingOption::USE_RIGHT_EXT]);
const bool left_ext_valid = (tray.id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID) && map_opt[MappingOption::USE_LEFT_EXT]);
if (!right_ext_valid && !left_ext_valid)
{
continue;
}
if (nozzle_has_ams_then_ignore_ext)
{
if (right_ext_valid && right_nozzle_has_ams)
@@ -208,12 +337,33 @@ namespace Slic3r
continue;
}
}
}
else
{
// Generic: every present virtual tray is the external spool of the extruder it
// addresses (VIRTUAL_TRAY_MAIN_ID - tray.id): 255 -> 0, 254 -> 1, ... . Keep to
// the selected side when only one legacy flag is enabled, and drop the spool when
// its extruder already has AMS filament in this mapping and suppression is on.
const int extruder_id = _virtual_tray_extruder_id(tray.id);
if (extruder_id < 0)
{
continue;
}
if (!_generic_extruder_in_selected_side(extruder_id, map_opt[MappingOption::USE_RIGHT_EXT], map_opt[MappingOption::USE_LEFT_EXT]))
{
continue;
}
if (nozzle_has_ams_then_ignore_ext && generic_extruders_with_ams.count(extruder_id) != 0)
{
continue;
}
}
FilamentInfo info;
_parse_tray_info(atoi(tray.id.c_str()), 0, DevAms::EXT_SPOOL, tray, info);
tray_filaments.emplace(std::make_pair(info.tray_id, info));
}
}
}
/////////////////////////
// Step 2: collect the distances of filaments_in_slicing to filaments_in_machine
@@ -13,6 +13,7 @@
#include "slic3r/GUI/DeviceCore/DevDefs.h"
#include "libslic3r/PrintConfig.hpp"
#include <boost/log/trivial.hpp>
#include <wx/app.h>
#include <wx/string.h>
#include <string>
#include "libslic3r/CommonDefs.hpp"
@@ -27,6 +28,7 @@
#include <unordered_map>
#include <optional>
#include <cmath>
#include "slic3r/GUI/GUI_App.hpp"
using json = nlohmann::json;
@@ -176,6 +178,10 @@ DevFirmwareVersionInfo DevNozzle::GetFirmwareInfo() const
int DevNozzle::GetLogicExtruderId() const
{
int total_ext_count = GetTotalExtruderCount();
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr &&
GUI::wxGetApp().preset_bundle->is_bbl_vendor();
if (is_bbl_vendor) {
if (total_ext_count == 1) {
return LOGIC_UNIQUE_EXTRUDER_ID;
} else if (total_ext_count == 2) {
@@ -190,8 +196,19 @@ int DevNozzle::GetLogicExtruderId() const
return LOGIC_UNIQUE_EXTRUDER_ID;
}
// For some reason, BBL's logical extruder ID is inverted:
// physical extruder id = 0 (MAIN_EXTRUDER_ID) vs logical extruder id = 1 (LOGIC_R_EXTRUDER_ID)
// Likely because logical id reads from left to right (left = 0, right = 1)
// For generic N extruders, this inversion does not apply.
if (IsOnRack()) return INVALID_EXTRUDER_ID;
return m_nozzle_id;
}
int DevNozzle::GetExtruderId() const
{
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr &&
GUI::wxGetApp().preset_bundle->is_bbl_vendor();
if (is_bbl_vendor) {
int total_ext_count = GetTotalExtruderCount();
if (total_ext_count == 1) {
return MAIN_EXTRUDER_ID;
@@ -206,6 +223,9 @@ int DevNozzle::GetExtruderId() const
return MAIN_EXTRUDER_ID;
}
return m_nozzle_id;
}
bool DevNozzle::AtLeftExtruder() const
{
assert(GetTotalExtruderCount() == 2);
+100 -54
View File
@@ -65,6 +65,7 @@
#include <unordered_set>
#include <utility>
#include <vector>
#include <wx/app.h>
#include <wx/colour.h>
#include <tuple>
#include <wx/app.h>
@@ -806,8 +807,12 @@ bool MachineObject::is_extrusion_cali_finished()
DevAmsTray *MachineObject::get_curr_tray()
{
const std::string& cur_ams_id = m_extder_system->GetCurrentAmsId();
if (cur_ams_id.compare(std::to_string(VIRTUAL_TRAY_MAIN_ID)) == 0) {
return &vt_slot[0];
if (devPrinterUtil::IsVirtualSlot(cur_ams_id)) {
for (auto& tray : vt_slot) {
if (tray.id == cur_ams_id)
return &tray;
}
return nullptr;
}
DevAms* curr_ams = get_curr_Ams();
@@ -1273,20 +1278,25 @@ int MachineObject::get_bed_temperature_limit()
bool MachineObject::is_filament_installed()
{
if (m_extder_system->GetTotalExtderCount() > 0) {
// right//or single
auto ext = m_extder_system->m_extders[MAIN_EXTRUDER_ID];
if (ext.m_ext_has_filament) {
// if (m_extder_system->GetTotalExtderCount() > 0) {
// // right//or single
// auto ext = m_extder_system->m_extders[MAIN_EXTRUDER_ID];
// if (ext.m_ext_has_filament) {
// return true;
// }
// }
// /*left*/
// if (m_extder_system->GetTotalExtderCount() > 1) {
// auto ext = m_extder_system->m_extders[DEPUTY_EXTRUDER_ID];
// if (ext.m_ext_has_filament) {
// return true;
// }
// }
for (auto& ext : m_extder_system->m_extders) {
if (ext.m_ext_has_filament)
return true;
}
}
/*left*/
if (m_extder_system->GetTotalExtderCount() > 1) {
auto ext = m_extder_system->m_extders[DEPUTY_EXTRUDER_ID];
if (ext.m_ext_has_filament) {
return true;
}
}
return false;
}
@@ -1798,8 +1808,7 @@ int MachineObject::command_ams_calibrate(int ams_id)
return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
}
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type,
int nozzle_temp_min, int nozzle_temp_max, const std::vector<std::string>& tray_colors, int tray_ctype)
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max)
{
int tag_tray_id = 0;
int tag_ams_id = ams_id;
@@ -1828,10 +1837,6 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s
j["print"]["nozzle_temp_min"] = nozzle_temp_min;
j["print"]["nozzle_temp_max"] = nozzle_temp_max;
j["print"]["tray_type"] = tray_type;
if (!tray_colors.empty()) {
j["print"]["cols"] = tray_colors;
j["print"]["ctype"] = tray_ctype;
}
return this->publish_json(j);
}
@@ -2632,13 +2637,10 @@ void MachineObject::reset()
json empty_j;
print_json.diff2all_base_reset(empty_j);
for (auto i = 0; i < vt_slot.size(); i++) {
vt_slot[i].reset();
if (i == 1) {
vt_slot.erase(vt_slot.begin() + 1);
}
}
for (auto& tray : vt_slot)
tray.reset();
if (vt_slot.size() > 1)
vt_slot.erase(vt_slot.begin() + 1, vt_slot.end());
// why: reset reuses MachineObject, so release its lazy subtask
// before dropping the pointer to prevent reconnect leaks.
if (subtask_) {
@@ -4119,25 +4121,25 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
if (jj.contains("vir_slot") && jj["vir_slot"].is_array()) {
for (auto it = jj["vir_slot"].begin(); it != jj["vir_slot"].end(); it++) {
auto vslot = parse_vt_tray(it.value().get<json>());
try {
const auto tray_json = it.value().get<json>();
if (!tray_json.is_object() || !tray_json.contains("id") || !tray_json["id"].is_string())
continue;
if (vslot.id == std::to_string(VIRTUAL_TRAY_MAIN_ID)) {
auto it = std::next(vt_slot.begin(), 0);
if (it != vt_slot.end()) {
vt_slot[0] = vslot;
}
else {
vt_slot.push_back(vslot);
}
}
else if (vslot.id == std::to_string(VIRTUAL_TRAY_DEPUTY_ID)) {
auto it = std::next(vt_slot.begin(), 1);
if (it != vt_slot.end()) {
vt_slot[1] = vslot;
}
else {
vt_slot.push_back(vslot);
const auto tray_id_str = tray_json["id"].get<std::string>();
int tray_id = 0;
const auto result = std::from_chars(tray_id_str.data(), tray_id_str.data() + tray_id_str.size(), tray_id);
if (result.ec != std::errc{} || result.ptr != tray_id_str.data() + tray_id_str.size()
|| tray_id < 0 || tray_id > VIRTUAL_TRAY_MAIN_ID)
continue;
auto vslot = parse_vt_tray(tray_json);
grow_virtual_tray_slots(vt_slot, static_cast<std::size_t>(tray_id));
const auto index = static_cast<std::vector<DevAmsTray>::size_type>(VIRTUAL_TRAY_MAIN_ID - tray_id);
vt_slot[index] = vslot;
}
catch (...) {
continue;
}
}
@@ -4168,6 +4170,13 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
try {
parse_new_info(jj);
} catch (...) {}
// Give every live device extruder an external virtual tray even when the payload
// carried no vir_slot/vt_tray data (non-Bambu), so the UI always has a slot per
// extruder. Runs after parse_new_info(), which refreshes the extruder count, and
// after the vir_slot/vt_tray handling above, which may have cleared the support
// flag and would otherwise hide these synthetic slots.
ensure_external_virtual_tray_slots();
#pragma endregion
} else if (jj["command"].get<std::string>() == "gcode_line") {
//ack of gcode_line
@@ -5273,18 +5282,50 @@ DevAmsTray MachineObject::parse_vt_tray(json vtray)
return vt_tray;
}
void MachineObject::ensure_external_virtual_tray_slots()
{
// Without a preset bundle the vendor layout is unknown, and Bambu owns its own fixed pair
// (255, 254) reported through vir_slot/vt_tray; in both cases leave vt_slot untouched.
if (wxTheApp == nullptr || GUI::wxGetApp().preset_bundle == nullptr ||
GUI::wxGetApp().preset_bundle->is_bbl_vendor()) {
return;
}
const int extruder_count = m_extder_system != nullptr ? m_extder_system->GetTotalExtderCount() : 0;
if (extruder_count <= 0) {
return;
}
// One slot per live extruder, addressed by VIRTUAL_TRAY_MAIN_ID - extruder_id. Grow with
// placeholders and only fill slots that do not exist yet, so a slot already populated from a
// reported tray keeps its metadata and the slot -> extruder mapping never shifts.
for (int extruder_id = 0; extruder_id < extruder_count; ++extruder_id) {
const int tray_id = VIRTUAL_TRAY_MAIN_ID - extruder_id;
grow_virtual_tray_slots(vt_slot, static_cast<std::vector<DevAmsTray>::size_type>(tray_id));
auto& tray = vt_slot[extruder_id];
// The constructor seeds id 255 without a placeholder flag. Mark any empty generic slot as
// synthetic, but leave a slot carrying reported filament data eligible for sync/mapping.
tray.is_slot_placeholder = !tray.is_exists && tray.setting_id.empty() && tray.m_fila_type.empty();
}
// A printer with an external-slot layout supports virtual trays even when the payload carried
// no vir_slot/vt_tray data, which would otherwise clear this flag and hide the synthetic slots.
ams_support_virtual_tray = true;
}
bool MachineObject::contains_tray(const std::string &ams_id, const std::string &tray_id) const
{
if (ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR) {
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor();
if (is_bbl_vendor && ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR)
return m_fila_system->GetAmsTray(ams_id, tray_id) != nullptr;
} else {
if (!is_bbl_vendor || ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR) {
for (const auto& tray : vt_slot) {
if (tray.id == ams_id) { return true; }
if (tray.id == ams_id) return true;
}
}
return false;
return !is_bbl_vendor && m_fila_system->GetAmsTray(ams_id, tray_id) != nullptr;
}
DevAmsTray MachineObject::get_tray(const std::string &ams_id, const std::string &tray_id) const
@@ -5294,13 +5335,19 @@ DevAmsTray MachineObject::get_tray(const std::string &ams_id, const std::string
return DevAmsTray(tray_id);
}
if (ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR) {
const bool is_bbl_vendor = wxTheApp != nullptr && GUI::wxGetApp().preset_bundle != nullptr && GUI::wxGetApp().preset_bundle->is_bbl_vendor();
if (is_bbl_vendor && ams_id != VIRTUAL_AMS_MAIN_ID_STR && ams_id != VIRTUAL_AMS_DEPUTY_ID_STR) {
auto tray = m_fila_system->GetAmsTray(ams_id, tray_id);
if (tray) { return *tray;};
if (tray) return *tray;
}
else {
else if (!is_bbl_vendor || ams_id == VIRTUAL_AMS_MAIN_ID_STR || ams_id == VIRTUAL_AMS_DEPUTY_ID_STR) {
for (const auto &tray : vt_slot) {
if (tray.id == ams_id) { return tray; }
if (tray.id == ams_id) return tray;
}
if (!is_bbl_vendor) {
auto tray = m_fila_system->GetAmsTray(ams_id, tray_id);
if (tray) return *tray;
}
}
@@ -5483,7 +5530,6 @@ void MachineObject::parse_new_info(json print)
is_support_pa_mode = (get_flag_bits_no_border(fun2, 3) == 1);
is_support_remote_dry = (get_flag_bits_no_border(fun2, 5) == 1);
is_support_check_track_switch_match_slice_printer = get_flag_bits_no_border(fun2, 19) == 1;
is_support_filament_manual_multi_color = (get_flag_bits_no_border(fun2, 23) == 1);
if (DevPrinterConfigUtil::support_print_check_firmware_for_tpu_left(printer_type)) {
m_firmware_support_print_tpu_left = get_flag_bits_no_border(fun2, 7) == 1;
+11 -2
View File
@@ -674,7 +674,6 @@ public:
bool is_support_remote_dry = false;
bool is_support_check_track_switch_match_slice_printer{false};
bool is_support_pa_mode{false};
bool is_support_filament_manual_multi_color{false};
std::optional<bool> m_firmware_support_print_tpu_left;
bool installed_upgrade_kit{false};
@@ -805,7 +804,7 @@ public:
int command_ams_switch_filament(bool switch_filament);
int command_ams_air_print_detect(bool air_print_detect);
int command_ams_calibrate(int ams_id);
int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max, const std::vector<std::string>& tray_colors = {}, int tray_ctype = 2);
int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max);
int command_ams_select_tray(std::string tray_id);
int command_ams_refresh_rfid(int ams_id, int slot_id);
int command_ams_control(std::string action);
@@ -951,6 +950,16 @@ public:
std::vector<DevAmsTray> vt_slot;
DevAmsTray parse_vt_tray(nlohmann::json vtray);
/**
* Give every live device extruder an external virtual tray slot (non-Bambu printers only).
*
* Slots are addressed by VIRTUAL_TRAY_MAIN_ID - extruder_id, so a slot already reported for an
* extruder keeps its tray metadata and sparse/out-of-order reports never shift the slot ->
* extruder association. Only missing topology slots are added, so the call is idempotent across
* refreshes. Bambu keeps its existing fixed pair reported through vir_slot/vt_tray.
*/
void ensure_external_virtual_tray_slots();
/*get ams slot info*/
bool contains_tray(const std::string &ams_id, const std::string &tray_id) const;
DevAmsTray get_tray(const std::string &ams_id, const std::string &tray_id) const;/*use contains_tray() check first*/
+1 -1
View File
@@ -96,7 +96,7 @@ void FilamentColorCodeQuery::LoadFromLocal()
const auto& fila_color_strs = json_data_item["fila_color"].get<std::vector<wxString>>();
for (const auto& color_str : fila_color_strs) {
if (color_str.size() > 3) /* Skip the value like "#0"*/{
fila_color.AddColor(wxColour(color_str));
fila_color.m_colors.emplace(wxColour(color_str));
}
}
}
-11
View File
@@ -87,17 +87,6 @@ struct FilamentColor
public:
size_t ColorCount() const noexcept { return m_colors.size(); }
// Preserve catalog endpoint order for display while retaining set-based lookup identity.
const std::vector<wxColour>& GetColors() const noexcept { return m_color_list; }
void AddColor(const wxColour& color)
{
if (m_colors.insert(color).second) m_color_list.push_back(color);
}
private:
std::vector<wxColour> m_color_list;
public:
void EndSet(int ctype)
{
+91 -15
View File
@@ -65,7 +65,6 @@
#include <string_view>
#include "slic3r/GUI/Selection.hpp"
#include "GLCanvas3D.hpp"
#include "SequentialPrintClearance.hpp"
#ifdef SLIC3R_CAD
#include "slic3r/GUI/CAD/DesignSketchTool.hpp" // Design tab: interactive 2D sketch tool
#endif
@@ -6398,16 +6397,17 @@ void GLCanvas3D::update_sequential_clearance()
//BBS: add the height logic
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
Polygons polygons;
std::vector<std::pair<Polygon, float>> height_polygons;
polygons.reserve(instances_count);
std::vector<SequentialClearanceInstance> convex_and_bounding_boxes;
convex_and_bounding_boxes.reserve(instances_count);
std::map<ObjectID, int> print_order;
for (const PrintObject* print_object : fff_print()->objects())
for (const PrintInstance& instance : print_object->instances())
print_order.emplace(instance.model_instance->id(), instance.model_instance->arrange_order);
height_polygons.reserve(instances_count);
std::vector<struct height_info> convex_and_bounding_boxes;
struct height_info
{
double instance_height;
BoundingBox bounding_box;
Polygon hull_polygon;
};
for (size_t i = 0; i < instance_transforms.size(); ++i) {
const size_t first_instance = convex_and_bounding_boxes.size();
const auto& instances = instance_transforms[i];
double rotation_z0 = instances.front()->get_rotation().z();
int index = 0;
@@ -6427,20 +6427,96 @@ void GLCanvas3D::update_sequential_clearance()
Polygon convex_hull(std::move(inst_pts));
BoundingBox bouding_box = convex_hull.bounding_box();
BoundingBox plate_bb = plate->get_bounding_box_crd();
const ObjectID instance_id = m_model->objects[i]->instances[index]->id();
double instance_height = m_model->objects[i]->get_instance_max_z(index++);
//skip the object for not current plate
if (!plate_bb.overlap(bouding_box))
continue;
convex_and_bounding_boxes.push_back({instance_height, bouding_box, convex_hull, instance_id});
convex_and_bounding_boxes.push_back({instance_height, bouding_box, convex_hull});
polygons.emplace_back(std::move(convex_hull));
}
sort_sequential_clearance_instances(convex_and_bounding_boxes.begin() + first_instance, convex_and_bounding_boxes.end(), print_order);
}
const auto& config = fff_print()->config();
const auto height_polygons = sequential_clearance_height_polygons(convex_and_bounding_boxes, config.printable_height,
config.extruder_clearance_height_to_lid, config.extruder_clearance_height_to_rod);
//sort the print instance
std::sort(convex_and_bounding_boxes.begin(), convex_and_bounding_boxes.end(),
[](auto &l, auto &r) {
auto ly1 = l.bounding_box.min.y();
auto ly2 = l.bounding_box.max.y();
auto ry1 = r.bounding_box.min.y();
auto ry2 = r.bounding_box.max.y();
auto inter_min = std::max(ly1, ry1);
auto inter_max = std::min(ly2, ry2);
auto lx = l.bounding_box.min.x();
auto rx = r.bounding_box.min.x();
if (inter_max - inter_min > 0)
return (lx < rx) || ((lx == rx)&&(ly1 < ry1));
else
return (ly1 < ry1);
});
/*bool has_interlaced_objects = false;
for (int k = 0; k < bounding_box_count; k++)
{
Polygon& convex = convex_and_bounding_boxes[k].hull_polygon;
BoundingBox& bbox = convex_and_bounding_boxes[k].bounding_box;
auto iy1 = bbox.min.y();
auto iy2 = bbox.max.y();
for (int i = k+1; i < bounding_box_count; i++)
{
Polygon& next_convex = convex_and_bounding_boxes[i].hull_polygon;
BoundingBox& next_bbox = convex_and_bounding_boxes[i].bounding_box;
auto py1 = next_bbox.min.y();
auto py2 = next_bbox.max.y();
auto inter_min = std::max(iy1, py1); // min y of intersection
auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists
if (inter_max - inter_min > 0) {
has_interlaced_objects = true;
break;
}
}
if (has_interlaced_objects)
break;
}*/
int bounding_box_count = convex_and_bounding_boxes.size();
double printable_height = fff_print()->config().printable_height;
double hc1 = fff_print()->config().extruder_clearance_height_to_lid;
double hc2 = fff_print()->config().extruder_clearance_height_to_rod;
for (int k = 0; k < bounding_box_count; k++)
{
Polygon& convex = convex_and_bounding_boxes[k].hull_polygon;
BoundingBox& bbox = convex_and_bounding_boxes[k].bounding_box;
auto iy1 = bbox.min.y();
auto iy2 = bbox.max.y();
double height = (k == (bounding_box_count - 1))?printable_height:hc1;
/*if (has_interlaced_objects) {
if ((k < (bounding_box_count - 1)) && (convex_and_bounding_boxes[k].instance_height > hc2)) {
height_polygons.emplace_back(std::make_pair(convex, hc2));
}
}
else {
if ((k < (bounding_box_count - 1)) && (convex_and_bounding_boxes[k].instance_height > hc1)) {
height_polygons.emplace_back(std::make_pair(convex, hc1));
}
}*/
for (int i = k+1; i < bounding_box_count; i++)
{
Polygon& next_convex = convex_and_bounding_boxes[i].hull_polygon;
BoundingBox& next_bbox = convex_and_bounding_boxes[i].bounding_box;
auto py1 = next_bbox.min.y();
auto py2 = next_bbox.max.y();
auto inter_min = std::max(iy1, py1); // min y of intersection
auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists
if (inter_max - inter_min > 0) {
height = hc2;
break;
}
}
if (height < convex_and_bounding_boxes[k].instance_height)
height_polygons.emplace_back(std::make_pair(convex, height));
}
// sends instances 2d hulls to be rendered
set_sequential_print_clearance_visible(true);
+15 -108
View File
@@ -252,11 +252,9 @@
#include "slic3r/GUI/ParamsDialog.hpp"
#include "slic3r/GUI/ParamsPanel.hpp"
#include "slic3r/GUI/PartPlate.hpp"
#include "slic3r/GUI/TextureLibrary.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/plugin/host/PluginPages.hpp"
#include "slic3r/plugin/host/PluginResourceTextures.hpp"
#include <wx/defs.h>
#include "slic3r/GUI/Widgets/WebView.hpp"
#include <cwchar>
@@ -335,23 +333,6 @@ static std::shared_ptr<OrcaCloudServiceAgent> get_orca_cloud_agent(NetworkAgent*
return agent ? std::dynamic_pointer_cast<OrcaCloudServiceAgent>(agent->get_cloud_agent()) : nullptr;
}
// Full {web} page URL for a same-site OrcaCloud path, or empty when it cannot be built. An already
// open cloud pane is navigated straight to it, without re-running the session handoff.
static wxString cloud_page_url(NetworkAgent* agent, const std::string& path)
{
if (path.empty())
return {};
auto orca = get_orca_cloud_agent(agent);
if (!orca)
return {};
std::string base = orca->get_cloud_base_url();
while (!base.empty() && base.back() == '/')
base.pop_back();
if (base.empty())
return {};
return from_u8(base + path);
}
void start_ping_test()
{
return;
@@ -3082,37 +3063,6 @@ void GUI_App::init_plugin_gui_wiring()
refresh_plugins_dialog();
switch_printer_agent_after_unload(capability.plugin_key);
});
plugin_mgr.subscribe_on_load_callback([](const std::string& plugin_key) {
if (wxTheApp == nullptr || wxGetApp().is_closing())
return;
PluginResourceTextures::instance().on_plugin_register(plugin_key);
Slic3r::GUI::bump_texture_library_generation();
});
plugin_mgr.subscribe_on_unload_callback([](const std::string& plugin_key) {
if (wxTheApp == nullptr || wxGetApp().is_closing())
return;
PluginResourceTextures::instance().on_plugin_deregister(plugin_key);
Slic3r::GUI::bump_texture_library_generation();
});
plugin_mgr.subscribe_on_capability_load_callback([](const PluginCapabilityId& capability) {
if (capability.type != PluginCapabilityType::ResourceTexture)
return;
if (wxTheApp == nullptr || wxGetApp().is_closing())
return;
PluginResourceTextures::instance().on_cap_register(capability);
Slic3r::GUI::bump_texture_library_generation();
});
plugin_mgr.subscribe_on_capability_unload_callback([](const PluginCapabilityId& capability) {
if (capability.type != PluginCapabilityType::ResourceTexture)
return;
if (wxTheApp == nullptr || wxGetApp().is_closing())
return;
PluginResourceTextures::instance().on_cap_deregister(capability);
Slic3r::GUI::bump_texture_library_generation();
});
// Packs already loaded before this wiring, or in tests where no callback fires.
PluginResourceTextures::instance().initial_sweep();
Slic3r::GUI::bump_texture_library_generation();
}
bool GUI_App::on_init_inner()
@@ -5484,7 +5434,12 @@ std::string GUI_App::handle_web_request(std::string cmd)
this->request_open_project({});
}
else if (command_str.compare("homepage_open_cloud") == 0) {
this->open_orca_cloud();
// Deferred: set_cloud_mode() may create a second native webview, which must
// not run inside this webview's script-message callback.
CallAfter([this]() {
if (WebViewPanel* home = WebViewPanel::if_built())
home->set_cloud_mode(true, cloud_webview_handoff());
});
}
else if (command_str.compare("homepage_close_cloud") == 0) {
CallAfter([]() {
@@ -5633,22 +5588,6 @@ std::string GUI_App::handle_web_request(std::string cmd)
return "";
}
void GUI_App::open_orca_cloud(const std::string& next)
{
// Remembered so a login while the pane is open re-seeds the same page rather than the default.
m_cloud_open_path = next;
// Deferred: set_cloud_mode() may create the second native webview, so this must not run inside
// the caller's frame (a webview script-message callback or an ImGui frame).
CallAfter([this]() {
if (mainframe != nullptr)
mainframe->select_tab(TAB_ID_HOME);
// The Home page is built lazily, so the tab switch may not have created it yet: ensure() builds
// it if needed, matching what showing the tab does, rather than dropping the open request.
if (WebViewPanel* home = WebViewPanel::ensure())
home->set_cloud_mode(true, cloud_webview_handoff(m_cloud_open_path), cloud_page_url(m_agent, m_cloud_open_path));
});
}
void GUI_App::handle_script_message(std::string msg, const std::string& provider)
{
try {
@@ -5926,7 +5865,7 @@ void GUI_App::on_update_machine_list(wxCommandEvent &evt)
}*/
}
wxString GUI_App::cloud_webview_handoff(const std::string& next)
wxString GUI_App::cloud_webview_handoff()
{
// The cloud pane loads the Start URL, not a token: the page checks the webview's own session
// and asks for a handoff itself (guide §4). Signed out, user_id is omitted and the page will
@@ -5934,7 +5873,7 @@ wxString GUI_App::cloud_webview_handoff(const std::string& next)
auto orca = get_orca_cloud_agent(m_agent);
if (!orca)
return {};
return from_u8(orca->build_web_session_start_url(next));
return from_u8(orca->build_web_session_start_url());
}
void GUI_App::request_cloud_web_session(const std::string& state)
@@ -6077,7 +6016,7 @@ void GUI_App::on_user_login_handle(wxCommandEvent &evt)
m_cloud_handoff_state.clear();
if (WebViewPanel* home = WebViewPanel::if_built())
if (home->cloud_mode_active())
home->set_cloud_mode(true, cloud_webview_handoff(m_cloud_open_path));
home->set_cloud_mode(true, cloud_webview_handoff());
}
@@ -6924,7 +6863,7 @@ void GUI_App::push_notification(const MachineObject* obj, wxString msg, wxStrin
}
}
bool GUI_App::reload_settings()
void GUI_App::reload_settings()
{
if (preset_bundle && m_agent) {
// Load user's personal presets
@@ -6932,7 +6871,7 @@ bool GUI_App::reload_settings()
int result = m_agent->get_user_presets(&user_presets);
if (result != 0) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get_user_presets failed with code " << result << ", skipping sync";
return false;
return;
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " cloud user preset number is: " << user_presets.size();
auto refresh_synced_ui = [this, user_presets = std::move(user_presets)]() mutable {
@@ -7007,30 +6946,13 @@ bool GUI_App::reload_settings()
refresh_synced_ui();
else
CallAfter(refresh_synced_ui);
return true;
}
return false;
}
void GUI_App::check_legacy_user_filament_presets(bool by_user)
{
// One check at a time: the startup check can come due while the manual one is open, and on macOS the menu bar
// stays usable while a modal dialog is up.
if (m_legacy_filament_check_running)
return;
if (!by_user && app_config->get_bool("legacy_user_filament_presets_dont_remind"))
return;
// The local copies may be older than the cloud ones another machine changed: migrating them would push changes
// made against stale copies and conflict with the cloud. The startup check waits for the presets to be pulled.
if (!by_user && m_user_preset_pull_pending) {
m_legacy_filament_check_due = true;
return;
}
// This check also answers a startup check that is still waiting.
m_legacy_filament_check_due = false;
m_legacy_filament_check_running = true;
ScopeGuard running_guard([this] { m_legacy_filament_check_running = false; });
const std::vector<LegacyUserFilamentPreset> legacy_presets = preset_bundle->legacy_user_filament_presets();
if (legacy_presets.empty()) {
if (by_user)
@@ -7075,9 +6997,6 @@ void GUI_App::check_legacy_user_filament_presets(bool by_user)
show_error(mainframe, format_wxstr(_L("Your filament presets could not be migrated. A backup of your presets was saved to:\n%1%"), backup));
return;
}
// Ask the sync loop to push on its next pass instead of its next 60 s round, which shortens the time the migrated
// presets stay unpushed.
m_sync_user_presets_now.store(true, std::memory_order_release);
preset_bundle->update_compatible(PresetSelectCompatibleType::Never);
mainframe->update_side_preset_ui();
for (auto tab : tabs_list) {
@@ -7844,7 +7763,6 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
BOOST_LOG_TRIVIAL(info) << "start_sync_service...";
// BBS
m_user_sync_token.reset(new int(0));
m_user_preset_pull_pending = true;
if (with_progress_dlg) {
// Mark a manual progress dialog as active so restart_sync_user_preset() ignores
// repeat triggers while it is on screen (prevents stacking modal dialogs).
@@ -7918,16 +7836,8 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
finishFn(ret == 0);
// Queued after the refresh reload_settings() queues, so it runs once the pulled presets are loaded.
if (ret == 0 && m_agent && !t.expired() && reload_settings())
CallAfter([this, t] {
// A stopped or restarted sync: the next sync, if one starts, pulls the presets again.
if (t.expired() || is_closing())
return;
m_user_preset_pull_pending = false;
if (m_legacy_filament_check_due)
check_legacy_user_filament_presets(false);
});
if (ret == 0 && m_agent && !t.expired())
reload_settings();
// For orca specific syncing
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent());
@@ -7942,11 +7852,8 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
// Sync once immediately, then every 60 seconds.
while (!t.expired()) {
++tick_tock;
// Sync once immediately, then every 60s, or right away when a force-push or a preset migration asked
// for it. The request is consumed on every tick, so one made before a scheduled pass is served by it
// instead of running a second pass straight after.
const bool push_now = m_sync_user_presets_now.exchange(false, std::memory_order_acq_rel);
if (tick_tock % 120 == 0 || push_now) {
// Sync once immediately, then every 60s, or right away when a force-push asked for it.
if (tick_tock % 120 == 0 || m_sync_user_presets_now.exchange(false, std::memory_order_acq_rel)) {
tick_tock = 0;
if (m_agent) {
if (!m_agent->is_user_login()) {
+3 -13
View File
@@ -355,9 +355,6 @@ private:
std::atomic<bool> m_restart_sync_pending {false};
std::atomic<bool> m_sync_user_preset_dlg_active {false}; // a manual "Sync Presets" progress dialog is on screen (see restart_sync_user_preset)
std::atomic<bool> m_sync_user_presets_now {false}; // request the sync loop to push user presets on its next tick
bool m_user_preset_pull_pending {false}; // set when a sync thread starts, cleared once its pull is applied; a failed pull or a stopped sync leaves it set
bool m_legacy_filament_check_due {false}; // the startup check waits for that pull
bool m_legacy_filament_check_running {false}; // a check is in progress, its dialog may be open
std::atomic<bool> m_migration_retry_pending {false};
bool m_is_dark_mode{ false };
bool m_adding_script_handler { false };
@@ -368,8 +365,6 @@ private:
// and a generation counter so a superseded mint/login result is dropped (guide §5, "only the
// newest request counts").
std::string m_cloud_handoff_state;
// The OrcaCloud path the open requests target, remembered so a login re-seed keeps the same page.
std::string m_cloud_open_path;
uint64_t m_cloud_handoff_gen { 0 };
bool m_show_error_msgdlg{false};
wxString m_info_dialog_content;
@@ -471,9 +466,6 @@ public:
#if wxUSE_WEBVIEW_EDGE
void init_webview_runtime();
#endif
// Switches to the Home tab and opens the OrcaCloud pane beside the homepage on `next`, a
// same-site OrcaCloud path (empty = the page's default). Work is deferred internally.
void open_orca_cloud(const std::string& next = std::string());
static unsigned get_colour_approx_luma(const wxColour& colour);
static bool dark_mode();
const wxColour get_label_default_clr_system();
@@ -580,7 +572,7 @@ public:
// called from the pane when the page asks for a session; it opens the desktop login dialog if
// signed out, otherwise mints a one-time token on a worker thread and loads the Complete URL
// in the same webview.
wxString cloud_webview_handoff(const std::string& next = std::string());
wxString cloud_webview_handoff();
void request_cloud_web_session(const std::string& state);
void mint_cloud_web_session(const std::string& state, uint64_t gen);
void on_cloud_web_session_minted(const std::string& state, uint64_t gen, const std::string& token_hash);
@@ -597,8 +589,7 @@ public:
void check_new_version_sf(bool show_tips = false, int by_user = 0);
// Offers to migrate user filament presets made against an older release that lost printers their parent
// listed then. At startup it asks on every start that finds such presets, unless the user chose not to be
// reminded; while the user presets sync with the cloud, it waits until they have been pulled. by_user also
// reports when there is nothing to migrate.
// reminded; by_user also reports when there is nothing to migrate.
void check_legacy_user_filament_presets(bool by_user);
bool process_network_msg(std::string dev_id, std::string msg);
void request_new_version(int by_user);
@@ -609,8 +600,7 @@ public:
std::string format_IP(const std::string& ip);
void show_dialog(wxString msg);
void push_notification(const MachineObject* obj, wxString msg, wxString title = wxEmptyString, UserNotificationStyle style = UserNotificationStyle::UNS_NORMAL);
// Returns false when the cloud presets could not be fetched, so nothing was reloaded.
bool reload_settings();
void reload_settings();
void remove_user_presets();
bool maybe_migrate_user_presets_on_login();
@@ -40,7 +40,6 @@
#include "slic3r/Utils/UndoRedo.hpp"
#include "GLGizmoUtils.hpp"
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <functional>
@@ -73,7 +72,6 @@
#include <array>
#include <cmath>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
@@ -99,48 +97,6 @@ namespace {
// bigger jump than the whole useful region.
constexpr float ImGuiLogSlider = float(ImGuiSliderFlags_Logarithmic);
// The scrollbar of the panel's scrolling areas (the panel body and the texture library). ImGui's stock
// scrollbar is a wide, square-cornered slab in a tinted track - against the flat panel it reads as a raw
// widget bolted onto the edge - so it is slimmed to a rounded thumb over an invisible track. The child
// background goes transparent with it, so the area reads as part of the panel.
void push_slim_scrollbar_style(float width, bool dark)
{
const ImVec4 grab = dark ? ImVec4(1.f, 1.f, 1.f, 0.26f) : ImVec4(0.f, 0.f, 0.f, 0.26f);
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, width);
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarRounding, 0.5f * width);
ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImVec4(0.f, 0.f, 0.f, 0.f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, grab);
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(grab.x, grab.y, grab.z, 0.45f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(grab.x, grab.y, grab.z, 0.65f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.f, 0.f, 0.f, 0.f));
}
void pop_slim_scrollbar_style()
{
ImGui::PopStyleColor(5);
ImGui::PopStyleVar(2);
}
// Cuts a UTF-8 string down to `max_w` pixels, ending it in "..." when it had to be cut.
std::string ellipsize(std::string s, float max_w, float font_size = 0.f)
{
const auto text_width = [&](const std::string &text) {
return font_size > 0.f
? ImGui::GetFont()->CalcTextSizeA(font_size, std::numeric_limits<float>::max(), 0.f, text.c_str()).x
: ImGui::CalcTextSize(text.c_str()).x;
};
if (text_width(s) <= max_w)
return s;
while (!s.empty() && text_width(s + "...") > max_w) {
unsigned char c;
do { // a whole code point at a time
c = static_cast<unsigned char>(s.back());
s.pop_back();
} while (!s.empty() && (c & 0xC0) == 0x80);
}
return s + "...";
}
// Uploads decoded 8-bit grayscale height data as an RGBA GPU texture for display in the panel.
// Shared by the per-layer thumbnail and the picker's library entries, which differ only in where
// their pixels came from.
@@ -2965,7 +2921,6 @@ void GLGizmoTextureDisplacement::run_uv_command(int cmd, float value)
// The same texture library the layer card opens; the panel renders it this frame.
m_picker_slot = m_active_layer_slot;
m_picker_open_request = true;
m_picker_reveal_selected = true;
} else if (cmd == int(Command::Unwrap)) {
// The solve runs only on this - painting, the seam angle and seam marking never trigger it.
m_uv_unwrap_pending = true;
@@ -4147,7 +4102,7 @@ void GLGizmoTextureDisplacement::set_layer_texture(TextureDisplacementLayer &lay
// the PNG the first time the layer is previewed or baked.
layer.image_data = tex->image_data;
// The user's own textures are listed most recently used first.
if (entry.source == TextureSource::User)
if (entry.is_user)
touch_user_texture(entry.path);
rebuild_preview();
@@ -4174,40 +4129,6 @@ void GLGizmoTextureDisplacement::import_custom_texture(TextureDisplacementLayer
set_layer_texture(layer, *entry);
}
void GLGizmoTextureDisplacement::ease_wheel_scroll(float &target, float &applied)
{
ImGuiIO &io = ImGui::GetIO();
const float scroll_now = ImGui::GetScrollY();
const float scroll_max = ImGui::GetScrollMaxY();
// Anything that moved the scroll without us - dragging the grab, a keyboard/gamepad nav step, the
// content shrinking under a clamped offset, a SetScroll*() call - has to re-seed the target, or the
// easing below would immediately drag the view back to where it last animated to.
if (applied < 0.f || std::abs(scroll_now - applied) > 0.5f)
target = scroll_now;
if (io.MouseWheel != 0.f && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows))
target -= io.MouseWheel * ImGui::GetFontSize() * 4.f;
// Whole pixels: ImGui floors whatever SetScrollY() is given, so a fractional target could never be
// reached and the "still gliding" test below would stay true forever, repainting the canvas for good.
target = std::floor(std::clamp(target, 0.f, scroll_max));
const float delta = target - scroll_now;
if (std::abs(delta) >= 1.f) {
// Exponential ease, formulated against the frame time so the glide takes the same wall time
// whether the canvas is running at 30 or 144 fps. The last sub-pixel step would be floored
// away, so land on the target outright once the remainder is that small.
const float t = 1.f - std::exp(-20.f * std::clamp(io.DeltaTime, 1.f / 240.f, 1.f / 15.f));
const float step = (std::abs(delta * t) < 1.f) ? delta : delta * t;
const float next = std::floor(scroll_now + step);
ImGui::SetScrollY(next);
applied = next;
m_parent.set_as_dirty(); // nothing else would redraw mid-glide once the mouse stops
} else {
applied = scroll_now;
}
}
void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &panel_min, const ImVec2 &panel_max)
{
ModelVolume *mv = texture_volume();
@@ -4228,51 +4149,13 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
const float spacing = std::round(m_imgui->scaled(0.4f));
const float grid_w = 4.f * tile + 3.f * spacing;
const float gap = m_imgui->scaled(0.5f);
const float scrollbar_w = m_imgui->scaled(0.5f);
const float ring_w = std::max(2.f, std::round(m_imgui->scaled(0.1f))); // the current texture's ring
const float frame_h = ImGui::GetFrameHeight();
const ImVec4 orca = ImGuiWrapper::COL_ORCA;
const ImGuiStyle &style = ImGui::GetStyle();
// A fixed size rather than a fit-to-content one. The width is the four-column grid, a gutter and the
// slim scrollbar beside it, plus the window padding, so the columns always fit and no horizontal
// scrollbar is needed; it derives from scaled values, so it tracks the DPI like the rest.
const float fixed_w = grid_w + spacing + scrollbar_w + 2.f * style.WindowPadding.x;
// BeginPopup() ORs in ImGuiWindowFlags_AlwaysAutoResize (deps_src/imgui/imgui.cpp:8371) and no flag
// turns it back off. A SetNextWindowSize() overrides it for each axis it sets (imgui.cpp:5983-5996),
// so an explicit size is the only way to fix the popup's size; the grid child below then scrolls.
//
// Top-aligned with the panel, which keeps it clear of the gizmo toolbar above a docked panel, and as
// tall as the panel or 70% of the window, whichever is taller, while keeping a gap inside the window.
// The height follows the window and the panel, never the content, so it does not jump as packs load or
// sections collapse.
const ImGuiViewport *viewport = ImGui::GetMainViewport();
const float vp_top = viewport->Pos.y + gap;
const float vp_bottom = viewport->Pos.y + viewport->Size.y - gap;
const float height = std::max(std::min(std::max(0.70f * viewport->Size.y, panel_max.y - panel_min.y), vp_bottom - vp_top),
2.f * frame_h);
const float popup_y = std::max(vp_top, std::min(panel_min.y, vp_bottom - height));
// Beside the panel rather than over it, so the layer being retextured stays in view: to its left when
// there is room (the docked panel sits against the right edge of the canvas), otherwise to its right.
const float width = viewport->Size.x > 0.f ? std::min(fixed_w, viewport->Size.x) : fixed_w;
const bool left = panel_min.x - gap - width >= 0.f;
const float popup_x = left ? panel_min.x - gap - width : panel_max.x + gap;
// Cond_Always, not Appearing: a viewport resize while the popup is open must move and resize it too.
ImGui::SetNextWindowPos(ImVec2(popup_x, popup_y), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(width, height), ImGuiCond_Always);
// Drawn as the panel's own sheet: its opaque background and rounding, no border. A popup is filled with
// PopupBg, which the panel's toolbar style leaves at ImGui's translucent near-black - a dark window
// beside a light panel, with whatever it covers showing through. Only Begin reads these, so they are
// popped at once and the tooltips shown from inside keep their own look.
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg));
ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, style.WindowRounding);
ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.f);
// The window itself never scrolls; the grid child below does.
const bool open = ImGui::BeginPopup(popup_id, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
if (!open)
const float popup_w = grid_w + 2.f * ImGui::GetStyle().WindowPadding.x;
const bool left = panel_min.x - gap - popup_w >= 0.f;
ImGui::SetNextWindowPos(ImVec2(left ? panel_min.x - gap : panel_max.x + gap, panel_min.y + m_imgui->scaled(4.f)),
ImGuiCond_Appearing, ImVec2(left ? 1.f : 0.f, 0.f));
if (!ImGui::BeginPopup(popup_id))
return;
if (layer == nullptr) {
ImGui::CloseCurrentPopup();
@@ -4288,49 +4171,16 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
m_tooltip_anchor_max = ImVec2(m_tooltip_anchor_min.x + ImGui::GetWindowSize().x,
m_tooltip_anchor_min.y + ImGui::GetWindowSize().y);
// The header stays put while the grid scrolls under it: the title says what the popup is for, and the
// close button is there for anyone who does not know that a click outside closes it.
bool close_clicked = false;
{
ImDrawList *hdl = ImGui::GetWindowDrawList();
const ImVec2 row = ImGui::GetCursorScreenPos();
ImGui::SetCursorScreenPos(ImVec2(row.x, row.y + 0.5f * (frame_h - ImGui::GetTextLineHeight())));
m_imgui->bold_text(_u8L("Choose a texture"));
const ImVec2 btn(ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x - frame_h, row.y);
ImGui::SetCursorScreenPos(btn);
close_clicked = ImGui::InvisibleButton("##close", ImVec2(frame_h, frame_h));
const bool hot = ImGui::IsItemHovered();
const ImVec2 c(btn.x + 0.5f * frame_h, btn.y + 0.5f * frame_h);
const float k = std::round(0.18f * frame_h);
const float line_w = std::max(1.f, std::round(m_imgui->scaled(0.08f)));
const ImU32 x_col = ImGui::GetColorU32(ImGuiCol_Text, hot ? 1.f : 0.6f);
if (hot)
hdl->AddCircleFilled(c, 0.5f * frame_h, ImGui::GetColorU32(ImGuiCol_Text, 0.08f));
hdl->AddLine(ImVec2(c.x - k, c.y - k), ImVec2(c.x + k, c.y + k), x_col, line_w);
hdl->AddLine(ImVec2(c.x - k, c.y + k), ImVec2(c.x + k, c.y - k), x_col, line_w);
ImGui::SetCursorScreenPos(ImVec2(row.x, row.y + frame_h + style.ItemSpacing.y));
ImGui::Separator();
}
m_imgui->push_bold_font();
m_imgui->text(_L("Choose a texture"));
m_imgui->pop_bold_font();
const std::vector<TextureLibraryEntry> &library = texture_library();
// A reinstalled pack can drop new bytes at a path already cached on this gizmo, and get_library_texture()
// caches a failure as well as a success, so a moved generation makes every cached entry suspect.
if (const uint64_t generation = texture_library_generation(); generation != m_library_generation) {
m_library_textures.clear();
m_library_generation = generation;
}
// The grid scrolls as the panel body does: the same slim scrollbar, and the wheel eased by
// ease_wheel_scroll() instead of ImGui's five-line jumps (hence NoScrollWithMouse).
push_slim_scrollbar_style(scrollbar_w, wxGetApp().dark_mode());
ImGui::BeginChild("##texture_grid", ImVec2(0.f, 0.f), false, ImGuiWindowFlags_NoScrollWithMouse);
ease_wheel_scroll(m_picker_scroll_target, m_picker_scroll_applied);
ImDrawList *dl = ImGui::GetWindowDrawList(); // the child's, so tiles clip to the grid
ImDrawList *dl = ImGui::GetWindowDrawList();
const ImU32 selected_col = ImGui::GetColorU32(ImGuiWrapper::COL_ORCA);
std::optional<TextureLibraryEntry> chosen;
std::optional<TextureLibraryEntry> to_remove;
bool import_clicked = false;
bool download_clicked = false;
// Tiles are placed on the four-column grid explicitly rather than flowed with SameLine(): a user tile
// overlays a remove button on its corner, and that button would otherwise be "the previous item" the
@@ -4346,15 +4196,16 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
ImGui::Dummy(ImVec2(grid_w, float(rows) * tile + float(rows - 1) * spacing));
}
};
// One section's tiles, in the order the section holds them (shipped alphabetically, pack textures by
// name, the user's own most recently used first - the order texture_library() produced); a user tile
// carries a remove button on hover. Returns how many it drew.
const auto grid = [&](const ImVec2 &origin, const std::vector<const TextureLibraryEntry *> &entries, bool removable) {
// One group's tiles: shipped textures alphabetically, or the user's own most recently used first (the
// order texture_library() keeps), those with a remove button on hover. Returns how many it drew.
const auto grid = [&](const ImVec2 &origin, bool user) {
int n = 0;
for (const TextureLibraryEntry *entry : entries) {
for (const TextureLibraryEntry &entry : library) {
if (entry.is_user != user)
continue;
ImGui::SetCursorScreenPos(tile_pos(origin, n++));
ImGui::PushID(entry->path.c_str());
const LibraryTexture *tex = get_library_texture(entry->path);
ImGui::PushID(entry.path.c_str());
const LibraryTexture *tex = get_library_texture(entry.path);
GLTexture *thumb = tex != nullptr ? tex->thumbnail.get() : nullptr;
bool clicked = false;
if (thumb != nullptr) {
@@ -4372,27 +4223,15 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
} else {
clicked = ImGui::Button("?", ImVec2(tile, tile));
}
if (removable)
if (user)
ImGui::SetItemAllowOverlap(); // the remove button drawn over its corner takes its own clicks
const ImVec2 a = ImGui::GetItemRectMin(), b = ImGui::GetItemRectMax();
const bool current = entry->path == layer->path;
const bool hovered = ImGui::IsItemHovered();
// The layer's current texture carries an accent ring, and the tile under the pointer a thinner,
// fainter one. Both are drawn inside the tile, where the grid's clip rect never cuts them.
if (current || hovered) {
const float w = current ? ring_w : std::round(0.5f * ring_w);
dl->AddRect(ImVec2(a.x + 0.5f * w, a.y + 0.5f * w), ImVec2(b.x - 0.5f * w, b.y - 0.5f * w),
ImGui::GetColorU32(current ? orca : ImVec4(orca.x, orca.y, orca.z, 0.6f)), 0.f, 0, w);
}
// Centred, as far as the scroll range allows, so the textures around it are in view too.
if (current && m_picker_reveal_selected)
ImGui::SetScrollFromPosY(0.5f * (a.y + b.y) - ImGui::GetWindowPos().y, 0.5f);
if (hovered)
panel_tooltip(entry->name);
if (entry.path == layer->path)
dl->AddRect(ImVec2(a.x - 1.f, a.y - 1.f), ImVec2(b.x + 1.f, b.y + 1.f), selected_col, 0.f, 0, 2.f);
if (ImGui::IsItemHovered())
panel_tooltip(entry.name);
// Clipped by the grid's clip rect, so a tile scrolled out of the grid or under its scrollbar
// does not offer its remove button. IsMouseHoveringRect() clips by default.
if (removable && ImGui::IsMouseHoveringRect(a, b)) {
if (user && ImGui::IsMouseHoveringRect(a, b)) {
const float r = std::round(tile * 0.17f);
const ImVec2 c(b.x - r - 2.f, a.y + r + 2.f);
ImGui::SetCursorScreenPos(ImVec2(c.x - r, c.y - r));
@@ -4405,162 +4244,27 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
if (hot)
panel_tooltip(_u8L("Remove from My textures"));
if (remove_clicked)
to_remove = *entry;
to_remove = entry;
}
ImGui::PopID();
if (clicked)
chosen = *entry;
chosen = entry;
}
return n;
};
// Every section collapses; the state is remembered for the app run. Pack keys use pack_name, a
// capability name unique per type across loaded packages and stable across reinstalls. This function-local
// static survives popup closes, never touches app_config, and resets on restart.
static std::map<std::string, bool> s_section_collapsed; // true = collapsed; absent = expanded
const auto split_pack_title = [](const std::string &title) -> std::pair<std::string, std::string> {
size_t split = std::string::npos;
for (size_t i = 0; i + 3 < title.size(); ++i)
if (title[i] == ' ' && (title[i + 1] == 'b' || title[i + 1] == 'B') &&
(title[i + 2] == 'y' || title[i + 2] == 'Y') && title[i + 3] == ' ')
split = i;
if (split == std::string::npos)
return { title, {} };
std::string name = title.substr(0, split);
while (!name.empty() && std::isspace(static_cast<unsigned char>(name.back())))
name.pop_back();
std::string attribution = title.substr(split);
size_t first = 0;
while (first < attribution.size() && std::isspace(static_cast<unsigned char>(attribution[first])))
++first;
attribution.erase(0, first);
if (name.empty() || attribution.empty())
return { title, {} };
return { name, attribution };
};
// A section heading, one row as wide as the grid: a click anywhere on it collapses or expands the
// section. Child headings indent their contents while keeping their counts right-aligned. Attribution
// lines use a smaller disabled style; the grid follows closer than the next heading does.
// Returns true when the section is open, so the caller knows whether to draw its contents.
const auto section_header = [&](const std::string &key, const std::string &title, size_t count, float indent,
const std::string &attribution, const std::string &full_title) {
bool &collapsed = s_section_collapsed[key]; // default-inserts false -> expanded
const ImVec2 row = ImGui::GetCursorScreenPos();
const bool two_lines = !attribution.empty();
const float line_h = ImGui::GetTextLineHeight();
const float small_font_size = ImGui::GetFontSize() * 0.8f;
const float small_line_h = line_h * 0.8f;
const float row_h = two_lines ? std::max(frame_h, line_h + small_line_h + spacing) : frame_h;
const ImVec2 row_max(row.x + grid_w, row.y + row_h);
if (ImGui::InvisibleButton(key.c_str(), ImVec2(grid_w, row_h)))
collapsed = !collapsed;
const bool hovered = ImGui::IsItemHovered();
if (hovered)
dl->AddRectFilled(row, row_max, ImGui::GetColorU32(ImGuiCol_Text, 0.05f), style.FrameRounding);
const float inset = std::round(0.25f * frame_h);
const float arrow = std::round(ImGui::GetFontSize() * 0.55f);
const float cx = row.x + inset + indent + 0.5f * arrow;
const float cy = row.y + 0.5f * row_h;
const float hs = 0.3f * arrow;
const ImU32 accent = ImGui::GetColorU32(orca);
if (collapsed)
dl->AddTriangleFilled(ImVec2(cx - 0.5f * hs, cy - hs), ImVec2(cx + 0.5f * hs, cy), ImVec2(cx - 0.5f * hs, cy + hs), accent);
else
dl->AddTriangleFilled(ImVec2(cx - hs, cy - 0.5f * hs), ImVec2(cx + hs, cy - 0.5f * hs), ImVec2(cx, cy + 0.5f * hs), accent);
const std::string n = std::to_string(count);
const float n_w = ImGui::CalcTextSize(n.c_str()).x;
const float tx = row.x + inset + indent + arrow + spacing;
const float max_text_w = std::max(0.f, row_max.x - inset - n_w - spacing - tx);
const std::string shown = ellipsize(title, max_text_w);
const std::string shown_attribution = two_lines ? ellipsize(attribution, max_text_w, small_font_size) : std::string{};
const float title_y = two_lines ? row.y + 0.5f * (row_h - line_h - small_line_h)
: row.y + 0.5f * (frame_h - line_h);
const float count_y = row.y + 0.5f * (row_h - line_h);
dl->AddText(ImVec2(tx, title_y), ImGui::GetColorU32(ImGuiCol_Text), shown.c_str());
if (two_lines)
dl->AddText(ImGui::GetFont(), small_font_size, ImVec2(tx, title_y + line_h),
ImGui::GetColorU32(ImGuiCol_TextDisabled), shown_attribution.c_str());
dl->AddText(ImVec2(row_max.x - inset - n_w, count_y), ImGui::GetColorU32(ImGuiCol_TextDisabled), n.c_str());
if (hovered && (shown != title || shown_attribution != attribution))
panel_tooltip(full_title); // a long plugin-supplied name, cut by the fixed width
ImGui::SetCursorScreenPos(ImVec2(row.x, row_max.y + spacing));
return !collapsed;
};
// Built-in is first, plugin packs are grouped under Texture libraries, and My
// textures stays last so its import tile and help line describe that group. Pack names identify both
// their child section and state.
struct Section
ImGui::TextDisabled("%s", _u8L("Built-in").c_str());
{
std::string key;
std::string title;
std::vector<const TextureLibraryEntry *> entries;
};
std::vector<Section> sections;
std::map<std::string, size_t> pack_section; // pack_name -> index in `sections`
sections.push_back({ "builtin:", _u8L("Built-in"), {} });
for (const TextureLibraryEntry &entry : library) {
if (entry.source == TextureSource::Shipped) {
sections.front().entries.push_back(&entry); // the section exists even when the library is empty
continue;
}
if (entry.source == TextureSource::User)
continue; // added below, so My textures stays last
if (entry.source != TextureSource::Plugin)
continue;
const auto [it, inserted] = pack_section.emplace(entry.pack_name, sections.size());
if (inserted)
sections.push_back({ "pack:" + entry.pack_name, entry.pack_name, {} });
sections[it->second].entries.push_back(&entry);
}
sections.push_back({ "user:", _u8L("My textures"), {} });
for (const TextureLibraryEntry &entry : library)
if (entry.source == TextureSource::User)
sections.back().entries.push_back(&entry);
const float child_indent = std::round(m_imgui->scaled(0.8f));
const Section &built_in = sections.front();
if (section_header(built_in.key, built_in.title, built_in.entries.size(), 0.f, {}, built_in.title)) {
const ImVec2 origin = ImGui::GetCursorScreenPos();
const int count = grid(origin, built_in.entries, false);
const int count = grid(origin, false);
end_grid(origin, count);
if (count == 0)
ImGui::TextDisabled("%s", _u8L("No textures found.").c_str());
}
size_t subscribed_count = 0;
for (size_t i = 1; i + 1 < sections.size(); ++i)
subscribed_count += sections[i].entries.size();
const std::string subscribed_title = _u8L("Texture libraries");
if (section_header("subscribed:", subscribed_title, subscribed_count, 0.f, {}, subscribed_title)) {
for (size_t i = 1; i + 1 < sections.size(); ++i) {
const Section &pack = sections[i];
const auto [name, attribution] = split_pack_title(pack.title);
if (!section_header(pack.key, name, pack.entries.size(), child_indent, attribution, pack.title))
continue;
ImGui::TextDisabled("%s", _u8L("My textures").c_str());
{
const ImVec2 origin = ImGui::GetCursorScreenPos();
const int count = grid(origin, pack.entries, false);
end_grid(origin, count);
}
ImGui::Dummy(ImVec2(0.f, spacing));
const std::string download_label = _u8L("Explore more texture libraries");
if (ImGui::Button(download_label.c_str(), ImVec2(grid_w, frame_h)))
download_clicked = true;
if (ImGui::IsItemHovered())
panel_tooltip(_u8L("Opens OrcaCloud in the Home page to browse for plugins."));
}
const Section &user = sections.back();
if (section_header(user.key, user.title, user.entries.size(), 0.f, {}, user.title)) {
const ImVec2 origin = ImGui::GetCursorScreenPos();
const int count = grid(origin, user.entries, true);
const int count = grid(origin, true);
ImGui::SetCursorScreenPos(tile_pos(origin, count));
if (ImGui::Button("+##import_texture", ImVec2(tile, tile)))
import_clicked = true;
@@ -4569,25 +4273,17 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
"bakes from and saved to your personal texture folder, kept separate from the "
"textures shipped with OrcaSlicer."));
end_grid(origin, count + 1);
// Belongs to this section: it describes the import tile and the remove-on-hover tiles, both of
// which live here, so it is hidden with them rather than left describing controls off-screen.
}
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + grid_w);
ImGui::TextDisabled("%s", _u8L("PNG, JPG or BMP - converted to a height map on import. Hover one of yours to remove it.").c_str());
ImGui::PopTextWrapPos();
}
// Done whether or not the current texture was drawn: one in a collapsed section stays where it is.
m_picker_reveal_selected = false;
ImGui::EndChild();
pop_slim_scrollbar_style();
if (chosen || import_clicked || close_clicked || download_clicked)
if (chosen || import_clicked)
ImGui::CloseCurrentPopup();
m_tooltip_anchor_min = prev_anchor_min;
m_tooltip_anchor_max = prev_anchor_max;
ImGui::EndPopup();
// The OrcaCloud plugin hub filtered to resource textures; the pane's session handoff redirects here.
if (download_clicked)
wxGetApp().open_orca_cloud("/app/plugins/plugin-hub?types=resource-textures");
// Only once the loop over the library is done: importing, picking (which reorders the recently used)
// and removing all change the library the loop was iterating.
@@ -6890,6 +6586,19 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
m_imgui->text(text);
m_imgui->pop_bold_font();
};
// Cuts a UTF-8 string down to `max_w` pixels, ending it in "..." when it had to be cut.
const auto ellipsize = [](std::string s, float max_w) {
if (ImGui::CalcTextSize(s.c_str()).x <= max_w)
return s;
while (!s.empty() && ImGui::CalcTextSize((s + "...").c_str()).x > max_w) {
unsigned char c;
do { // a whole code point at a time
c = static_cast<unsigned char>(s.back());
s.pop_back();
} while (!s.empty() && (c & 0xC0) == 0x80);
}
return s + "...";
};
// Label column + a slider filling the rest of the row, value printed inside the track.
// `right_inset` keeps a layer card's inner padding.
const auto slider_label = [&](const wxString &label) {
@@ -7082,11 +6791,52 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
style.WindowPadding.y - style.ItemSpacing.y);
const float body_h = m_panel_body_h > 0.f ? std::min(m_panel_body_h + 1.f, max_body_h) : max_body_h;
push_slim_scrollbar_style(m_imgui->scaled(0.5f), dark);
// NoScrollWithMouse: ease_wheel_scroll() eases the scroll instead of teleporting five text lines per
// notch, which on blocks this tall lost the reader's place.
// ImGui's stock scrollbar is a wide, square-cornered slab in a tinted track - against this flat panel
// it reads as a raw widget bolted onto the edge. Slim it to a rounded thumb over an invisible track.
const float scrollbar_w = m_imgui->scaled(0.5f);
const ImVec4 grab = dark ? ImVec4(1.f, 1.f, 1.f, 0.26f) : ImVec4(0.f, 0.f, 0.f, 0.26f);
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, scrollbar_w);
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarRounding, 0.5f * scrollbar_w);
ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, ImVec4(0.f, 0.f, 0.f, 0.f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, grab);
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(grab.x, grab.y, grab.z, 0.45f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(grab.x, grab.y, grab.z, 0.65f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.f, 0.f, 0.f, 0.f));
// NoScrollWithMouse: the wheel is handled below so the scroll can be eased instead of teleporting
// five text lines per notch, which on blocks this tall lost the reader's place.
ImGui::BeginChild("##td_body", ImVec2(panel_w, body_h), false, ImGuiWindowFlags_NoScrollWithMouse);
ease_wheel_scroll(m_panel_scroll_target, m_panel_scroll_applied);
{
ImGuiIO &io = ImGui::GetIO();
const float scroll_now = ImGui::GetScrollY();
const float scroll_max = ImGui::GetScrollMaxY();
// Anything that moved the scroll without us - dragging the grab, a keyboard/gamepad nav step, the
// content shrinking under a clamped offset - has to re-seed the target, or the easing below would
// immediately drag the view back to where it last animated to.
if (m_panel_scroll_applied < 0.f || std::abs(scroll_now - m_panel_scroll_applied) > 0.5f)
m_panel_scroll_target = scroll_now;
if (io.MouseWheel != 0.f && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows))
m_panel_scroll_target -= io.MouseWheel * ImGui::GetFontSize() * 4.f;
// Whole pixels: ImGui floors whatever SetScrollY() is given, so a fractional target could never be
// reached and the "still gliding" test below would stay true forever, repainting the canvas for good.
m_panel_scroll_target = std::floor(std::clamp(m_panel_scroll_target, 0.f, scroll_max));
const float delta = m_panel_scroll_target - scroll_now;
if (std::abs(delta) >= 1.f) {
// Exponential ease, formulated against the frame time so the glide takes the same wall time
// whether the canvas is running at 30 or 144 fps. The last sub-pixel step would be floored
// away, so land on the target outright once the remainder is that small.
const float t = 1.f - std::exp(-20.f * std::clamp(io.DeltaTime, 1.f / 240.f, 1.f / 15.f));
const float step = (std::abs(delta * t) < 1.f) ? delta : delta * t;
const float next = std::floor(scroll_now + step);
ImGui::SetScrollY(next);
m_panel_scroll_applied = next;
m_parent.set_as_dirty(); // nothing else would redraw mid-glide once the mouse stops
} else {
m_panel_scroll_applied = scroll_now;
}
}
// Everything that changes the layer list is deferred until the list is no longer being drawn:
// removing or reordering shifts mv->texture_displacement_layers under the pointers the loop holds,
@@ -7333,7 +7083,6 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
const auto open_picker = [&](int slot) {
m_picker_slot = slot;
m_picker_open_request = true;
m_picker_reveal_selected = true;
};
// The six-dot drag icon, drawn into `list` over [x, x + w] x [y, y + h].
const auto draw_drag_icon = [&](ImDrawList *list, float x, float y, float w, float h, ImU32 tint) {
@@ -8328,7 +8077,8 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
// height of everything above, whether or not it is scrolled.
m_panel_body_h = ImGui::GetCursorPosY() - style.ItemSpacing.y;
ImGui::EndChild();
pop_slim_scrollbar_style();
ImGui::PopStyleColor(5);
ImGui::PopStyleVar(2);
// The layer-list changes deferred from the loop above.
if (slot_to_remove >= 0)
@@ -428,10 +428,6 @@ private:
void render_texture_library_popup(const ImVec2 &panel_min, const ImVec2 &panel_max);
int m_picker_slot = -1;
bool m_picker_open_request = false;
// Set when the popup opens, so the next layout scrolls the layer's current texture into view.
bool m_picker_reveal_selected = false;
float m_picker_scroll_target = 0.f; // see ease_wheel_scroll()
float m_picker_scroll_applied = -1.f;
void set_layer_texture(TextureDisplacementLayer &layer, const TextureLibraryEntry &entry);
void import_custom_texture(TextureDisplacementLayer &layer);
@@ -1066,8 +1062,6 @@ private:
// Library textures the picker has shown at least once, keyed by file path (see LibraryTexture).
std::map<std::string, LibraryTexture> m_library_textures;
// Generation of the library the above were cached against; a change clears them.
uint64_t m_library_generation = 0;
// Everything the *unwrap* depends on. update_uv_editor() runs from rebuild_preview(), i.e. on
// every stroke end and every slider release - but depth/tiling/rotation/offset/blend change
@@ -1109,15 +1103,12 @@ private:
// The config key behind m_undocked.
static constexpr const char *UNDOCKED_CONFIG_KEY = "texture_displacement_panel_undocked";
// Smooth scrolling for a child window, called inside it: the panel body (everything between the header
// and the pinned Bake footer) and the texture library's grid. ImGui jumps a fixed number of lines per
// wheel notch, which on tall layer cards reads as a hard jolt rather than a scroll. The child sets
// ImGuiWindowFlags_NoScrollWithMouse, and the wheel moves `target` instead; the real scroll is eased
// toward it over the following frames. `applied` is what the easing wrote last frame, <0 until the
// first one.
void ease_wheel_scroll(float &target, float &applied);
// Smooth scrolling for the panel body (everything between the header and the pinned Bake footer).
// ImGui jumps a fixed number of lines per wheel notch, which on tall layer cards reads as a hard
// jolt rather than a scroll. The wheel is intercepted (ImGuiWindowFlags_NoScrollWithMouse) and
// moves a *target* offset instead; the real scroll is eased toward it over the following frames.
float m_panel_scroll_target = 0.f;
float m_panel_scroll_applied = -1.f;
float m_panel_scroll_applied = -1.f; // what the easing wrote last frame; <0 until the first one
// Last frame's body content height and footer height. The body is a child window that has to be
// given its height before its content is laid out, so it is sized from the previous frame: as tall
// as its content, capped so the footer still fits above the bottom of the canvas.
+4 -6
View File
@@ -83,13 +83,11 @@ namespace Slic3r::GUI::GLGizmoUtils {
bool clicked = toolbar_circular_button(icon, "##wiki_guide_btn", scale);
if (url && *url && clicked)
wxLaunchDefaultBrowser(wxString::FromUTF8(url));
// ImGuiWrapper::tooltip keeps the text readable on the dark tooltip background whatever text colour the
// host window pushed; a negative wrap position keeps the URL on one line.
if (ImGui::IsItemHovered()){
if(url && *url)
wxGetApp().imgui()->tooltip(_u8L("Wiki Guide") + "\n" + url, -1.f);
ImGui::SetTooltip("%s\n%s", _u8L("Wiki Guide").c_str(), url);
else
wxGetApp().imgui()->tooltip(_u8L("Wiki Guide"), -1.f);
ImGui::SetTooltip("%s", _u8L("Wiki Guide").c_str());
}
return clicked; // for dynamically generated links
}
@@ -103,9 +101,9 @@ namespace Slic3r::GUI::GLGizmoUtils {
wxLaunchDefaultBrowser(wxString::FromUTF8(url));
if (ImGui::IsItemHovered()){
if(url && *url)
wxGetApp().imgui()->tooltip(_u8L("Video Guide") + "\n" + url, -1.f);
ImGui::SetTooltip("%s\n%s", _u8L("Video Guide").c_str(), url);
else
wxGetApp().imgui()->tooltip(_u8L("Video Guide"), -1.f);
ImGui::SetTooltip("%s", _u8L("Video Guide").c_str());
}
return clicked; // for dynamically generated links
}
@@ -857,12 +857,11 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
if (render_combo(imgui_wrapper, "", modes, selection_idx, 0, coord_combo_width)) {
combox_changed = true;
}
ImGuiWrapper::pop_combo_style();
// After pop_combo_style(): its light popup background would hide the tooltip's white text.
if (ImGui::IsItemHovered()) {
auto tooltip_str = _L("Coordinate system used for transform actions.");
imgui_wrapper->tooltip(tooltip_str, imgui_wrapper->calc_text_size(tooltip_str).x + 3 * space_size);
}
ImGuiWrapper::pop_combo_style();
// ORCA use TextColored to match axes color
float offset_to_center = (unit_size - ImGui::CalcTextSize("O").x) / 2;
@@ -965,12 +964,11 @@ void GizmoObjectManipulation::do_render_move_window(ImGuiWrapper *imgui_wrapper,
if (render_combo(imgui_wrapper, "", align_modes, align_mode_idx, 0, coord_combo_width))
m_align_to_parent = align_mode_idx + 1 == align_modes.size();
ImGui::PopID();
ImGuiWrapper::pop_combo_style();
// After pop_combo_style(): its light popup background would hide the tooltip's white text.
if (ImGui::IsItemHovered()) {
wxString tooltip_str = _L("Reference for alignment");
imgui_wrapper->tooltip(tooltip_str, imgui_wrapper->calc_text_size(tooltip_str).x + 3 * space_size);
}
ImGuiWrapper::pop_combo_style();
const bool dark_mode = wxGetApp().dark_mode();
int current_axis = -1;
@@ -1379,12 +1377,11 @@ void GizmoObjectManipulation::do_render_scale_input_window(ImGuiWrapper* imgui_w
if (render_combo(imgui_wrapper, "", modes, selection_idx, 0, coord_combo_width)) {
combox_changed = true;
}
ImGuiWrapper::pop_combo_style();
// After pop_combo_style(): its light popup background would hide the tooltip's white text.
if (ImGui::IsItemHovered()) {
auto tooltip_str = _L("Coordinate system used for transform actions.");
imgui_wrapper->tooltip(tooltip_str, imgui_wrapper->calc_text_size(tooltip_str).x + 3 * space_size);
}
ImGuiWrapper::pop_combo_style();
// ORCA use TextColored to match axes color
float offset_to_center = (unit_size - ImGui::CalcTextSize("O").x) / 2;
+3 -5
View File
@@ -505,12 +505,10 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt)
io.MouseDown[0] = evt.LeftIsDown();
io.MouseDown[1] = evt.RightIsDown();
io.MouseDown[2] = evt.MiddleIsDown();
// Only a vertical wheel event with a direction is a wheel step. A macOS trackpad reports sideways motion
// as separate horizontal events, and its wheel events often carry a rotation of 0: wx truncates the
// sub-pixel deltas to whole ones and passes on events with no delta at all. Read as vertical steps,
// either kind scrolls against the gesture, and scrolling up bounces back and forth.
if (evt.GetWheelAxis() == wxMOUSE_WHEEL_VERTICAL && evt.GetWheelRotation() != 0)
float wheel_delta = static_cast<float>(evt.GetWheelDelta());
if (wheel_delta != 0.0f) {
io.MouseWheel = evt.GetWheelRotation() > 0 ? 1.f : -1.f;
}
unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0);
m_mouse_buttons = buttons;
-15
View File
@@ -816,18 +816,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
bool MainFrame::handle_global_shortcut(const KeyChord& chord)
{
// ImGui draws its text fields (the text tool's box, a sketch value field) on the GL canvas, so
// while one of them is typed into the focused wx window is that canvas or an in-canvas frame:
// neither is a wxControl, so focus_keeps_space() cannot see it and CHAR_HOOK takes the chord
// here first -- Space opened the Speed Dial, Ctrl+P the Preferences, and the field never saw
// the key. ImGui raises WantCaptureKeyboard while one of its items owns the keyboard, so leave
// the key to it then. A focused wxControl (text field, list, web view) is untouched: those
// keep their own keys, and ImGui's flag can outlive the focus moving to one of them.
wxWindow* const focus = wxWindow::FindFocus();
ImGuiWrapper* const imgui = wxGetApp().imgui();
if (focus != nullptr && imgui != nullptr && imgui->want_keyboard() && dynamic_cast<wxControl*>(focus) == nullptr)
return false;
const std::optional<Shortcut> shortcut = wxGetApp().shortcuts().lookup(ShortcutContext::Global, chord);
if (!shortcut.has_value())
return false;
@@ -1739,9 +1727,6 @@ void MainFrame::fit_tab_labels()
for (size_t i = count - 1; i >= 1; --i) {
int right = ScreenToClient(m_slice_option_btn->ClientToScreen({})).x;
int left = sizer->GetSize().GetWidth();
// The overflow button sits between the tab buttons and the side tools, so it is not free space.
if (const wxWindow* overflow = ctrl->GetOverflowButton())
left += overflow->GetSize().GetWidth();
if (right - left - FromDIP(15) > 0) return;
ctrl->SetCompact(i, true);
m_tabpanel->Refresh();
-1
View File
@@ -57,7 +57,6 @@ public:
// ORCA: a companion widget shown right after the tab buttons (before any side_tools), e.g.
// an overflow indicator. Pass nullptr to remove it; ownership stays with the caller.
void SetOverflowButton(wxWindow* button);
wxWindow* GetOverflowButton() const { return m_overflow_button; }
private:
wxFlexGridSizer* m_buttons_sizer;
+181 -15
View File
@@ -20,6 +20,7 @@
#include <clocale>
#include <cstddef>
#include <algorithm>
#include <cctype>
#include <ios>
#include <iomanip>
#include <cstring>
@@ -2361,6 +2362,10 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man
return false;
}
const Preset &selected_preset = preset_bundle->printers.get_selected_preset();
const Preset *parent_preset = preset_bundle->printers.get_preset_parent(selected_preset);
const bool inherits_system_preset = selected_preset.is_user() && parent_preset && parent_preset->is_system;
if (!inherits_system_preset) {
if (!this->plater)
return false;
@@ -2369,6 +2374,7 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man
printer_tab->select_preset(machine_preset->name);
});
}
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " go on sync_extruder_list";
const Preset &cur_preset = preset_bundle->printers.get_selected_preset();
int extruder_nums = preset_bundle->get_printer_extruder_count();
@@ -2379,7 +2385,19 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man
assert(physical_extruder_map->values.size() == extruder_nums);
extruder_map = physical_extruder_map->values;
}
assert(obj->GetExtderSystem()->GetTotalExtderCount() == extruder_nums);
const int device_extruder_count = obj->GetExtderSystem()->GetTotalExtderCount();
if (device_extruder_count != extruder_nums) {
MessageDialog dlg(
this->plater,
format_wxstr(
_L("The connected printer reports %1% extruder(s), but the selected printer preset has %2%. "
"Update the preset to match the printer before syncing."),
device_extruder_count, extruder_nums),
_L("Sync printer information"),
wxOK | wxICON_WARNING);
dlg.ShowModal();
return false;
}
// Multi-nozzle: resolve the nozzle option (pops MultiNozzleSyncDialog when is_manual). No-op for every
// existing printer — support_multi_nozzle is false unless some extruder_max_nozzle_count entry is a real
@@ -2458,26 +2476,60 @@ bool Sidebar::priv::sync_extruder_list(bool &only_external_material, bool is_man
int main_index = obj->is_main_extruder_on_left() ? 0 : 1;
int deputy_index = obj->is_main_extruder_on_left() ? 1 : 0;
// Nozzle diameters are not changed by device sync. If the device reports a different or unavailable
// diameter, warn and leave the preset and controls alone while syncing AMS and other printer info.
const ConfigOptionFloats *preset_nozzle_diameter =
preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter");
bool nozzle_diameters_match = false;
if (preset_nozzle_diameter != nullptr && preset_nozzle_diameter->values.size() == nozzle_diameters.size()) {
nozzle_diameters_match = true;
for (size_t index = 0; index < nozzle_diameters.size(); ++index) {
// A diameter the device does not report (missing, zero, negative or not a finite number) never matches.
const double device_diameter = nozzle_diameters[index];
const double preset_diameter = preset_nozzle_diameter->values[index];
if (!std::isfinite(device_diameter) || device_diameter <= 0.0 ||
!std::isfinite(preset_diameter) || preset_diameter <= 0.0 ||
std::fabs(device_diameter - preset_diameter) > EPSILON) {
nozzle_diameters_match = false;
break;
}
}
}
if (!nozzle_diameters_match) {
if (is_manual) {
MessageDialog dlg(
this->plater,
_L("The nozzle diameter reported by the connected printer does not match the current printer preset. "
"The nozzle diameter and the printer preset will not be synchronized. "
"Select the printer preset that matches the nozzle installed on the printer, then sync again."),
_L("Sync printer information"), wxICON_WARNING | wxOK);
dlg.ShowModal();
} else {
// An automatic sync runs on every device update, so log the mismatch instead of popping a dialog.
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << __LINE__
<< " nozzle diameter mismatch between connected printer and printer preset,"
" skip syncing the nozzle diameter";
}
}
if (extruder_nums > 1) {
if (nozzle_diameters_match) {
int left_index = left_extruder->combo_diameter->FindString(get_diameter_string(nozzle_diameters[0]));
int right_index = left_extruder->combo_diameter->FindString(get_diameter_string(nozzle_diameters[1]));
assert(left_index != -1 && right_index != -1);
left_extruder->combo_diameter->SetSelection(left_index);
right_extruder->combo_diameter->SetSelection(right_index);
is_switching_diameter = true;
switch_diameter(false);
is_switching_diameter = false;
}
AMSCountPopupWindow::SetAMSCount(deputy_index, deputy_4, deputy_1);
AMSCountPopupWindow::SetAMSCount(main_index, main_4, main_1);
AMSCountPopupWindow::UpdateAMSCount(0, left_extruder);
AMSCountPopupWindow::UpdateAMSCount(1, right_extruder);
} else {
if (nozzle_diameters_match) {
int index = single_extruder->combo_diameter->FindString(get_diameter_string(nozzle_diameters[0]));
assert(index != -1);
single_extruder->combo_diameter->SetSelection(index);
is_switching_diameter = true;
switch_diameter(true);
is_switching_diameter = false;
}
}
// set nozzle volume type after switching prset, so this value can override the old value stored in conf
@@ -6069,8 +6121,12 @@ void Sidebar::on_bed_type_change(BedType bed_type)
*
* Return Value:
* - Map key encoding:
* - Virtual trays: 0x10000 + vt_tray.id (first/main extruder), or just vt_tray.id (secondary)
* - Virtual trays: 0x10000 + vt_tray.id for the main extruder's slot, or just vt_tray.id for the
* others. Bambu's fixed pair is 255 (main) / 254 (secondary); a generic printer identifies each
* slot by its own id (extruder = VIRTUAL_TRAY_MAIN_ID - id: 255 -> 0, 254 -> 1, ...)
* - AMS trays: 0x10000 + (ams_id * 4 + slot_id) (main extruder), or (ams_id * 4 + slot_id) (secondary)
* - Generic (non-Bambu) units are not four slots wide: their trays index by a cumulative lane
* count across units instead of ams_id * 4 + slot_id
* - The 0x10000 flag indicates the main/right extruder
* - Map value: DynamicPrintConfig with filament properties (id, type, color, etc.)
*
@@ -6090,7 +6146,13 @@ std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject
}
}
auto build_tray_config = [](DevAmsTray const &tray, std::string const &name, std::string ams_id, std::string slot_id) {
const bool is_bbl_vendor = wxTheApp != nullptr && wxGetApp().preset_bundle != nullptr && wxGetApp().preset_bundle->is_bbl_vendor();
// Diagnostic: keys produced from virtual (external) trays, so the final summary can label each
// output entry's origin as virtual vs AMS.
std::set<int> virtual_slot_keys;
auto build_tray_config = [is_bbl_vendor](DevAmsTray const &tray, std::string const &name, std::string ams_id, std::string slot_id, int extruder_id) {
BOOST_LOG_TRIVIAL(info) << boost::format("build_filament_ams_list: name %1% setting_id %2% type %3% color %4%")
% name % tray.setting_id % tray.m_fila_type % tray.color;
DynamicPrintConfig tray_config;
@@ -6105,6 +6167,12 @@ std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject
tray_config.set_key_value("filament_colour_type", new ConfigOptionStrings{std::to_string(tray.ctype)});
tray_config.set_key_value("filament_exist", new ConfigOptionBools{tray.is_exists});
tray_config.set_key_value("filament_slot_placeholder", new ConfigOptionBools{tray.is_slot_placeholder});
// Transient (never persisted) per-extruder association of a generic slot, so the sync and
// the slicing per-extruder grouping keep the real extruder instead of the legacy left/right
// buckets. Bambu entries omit it and keep the legacy key-only convention.
if (!is_bbl_vendor) {
tray_config.set_key_value("device_tray_extruder_id", new ConfigOptionInt(extruder_id));
}
std::optional<FilamentBaseInfo> info;
if (wxGetApp().preset_bundle) {
info = wxGetApp().preset_bundle->get_filament_by_filament_id(tray.setting_id);
@@ -6117,10 +6185,44 @@ std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject
};
if (obj->ams_support_virtual_tray) {
int extruder = 0x10000; // Main (first) extruder at right
for (auto & vt_tray : obj->vt_slot) {
filament_ams_list.emplace(extruder + stoi(vt_tray.id), build_tray_config(vt_tray, "Ext",vt_tray.id, "0"));//254 or 255
extruder = 0;
for (size_t index = 0; index < obj->vt_slot.size(); ++index) {
auto & vt_tray = obj->vt_slot[index];
int vt_id = 0;
if (vt_tray.id.empty()) continue;
if (!std::all_of(vt_tray.id.begin(), vt_tray.id.end(), [](unsigned char c) { return std::isdigit(c) != 0; })) continue;
try { vt_id = std::stoi(vt_tray.id); } catch (...) { continue; }
if (vt_id < 0 || vt_id > VIRTUAL_TRAY_MAIN_ID) continue;
// Enumerate the extruder ordinal (the vt_slot index) and validate the expected tray id
// instead of trusting the vector size: a stray filler or a stale id for a non-live
// extruder must never surface as a real tray. Bambu keeps its fixed pair untouched.
if (!is_bbl_vendor) {
const int expected_id = VIRTUAL_TRAY_MAIN_ID - static_cast<int>(index);
if (vt_id != expected_id || !devPrinterUtil::IsVirtualSlot(vt_id)) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": skip vt_slot index %1% id %2% expected %3%") % index % vt_id % expected_id;
continue;
}
}
// Bambu keeps its fixed pair with the leading (main/right) slot marked 0x10000. On a
// generic printer every external slot is identified by its own virtual tray id
// (extruder = VIRTUAL_TRAY_MAIN_ID - id: 255 -> 0, 254 -> 1, ...), so mark the main
// extruder's slot by identity rather than by position in vt_slot. The map key then
// keeps each slot bound to its extruder across refreshes and sparse reports.
const bool is_main_slot = is_bbl_vendor ? (&vt_tray == &obj->vt_slot.front())
: (vt_id == VIRTUAL_TRAY_MAIN_ID);
const int key = (is_main_slot ? 0x10000 : 0) + vt_id;
const auto inserted = filament_ams_list.emplace(key,
build_tray_config(vt_tray, "Ext", vt_tray.id, "0", VIRTUAL_TRAY_MAIN_ID - vt_id));
virtual_slot_keys.insert(key);
if (!inserted.second) {
const auto &existing = inserted.first->second;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": virtual insert collision key %1% incoming_id %2% existing_origin %3% existing_ams %4% existing_slot %5% existing_type %6% existing_id %7%")
% key % vt_id
% (virtual_slot_keys.count(key) ? "virtual" : "ams")
% existing.opt_string("ams_id", 0u)
% existing.opt_string("slot_id", 0u)
% existing.opt_string("filament_type", 0u)
% existing.opt_string("filament_id", 0u);
}
}
}
@@ -6140,12 +6242,62 @@ std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject
for (auto ams : list) {
int ams_id = std::stoi(ams.first);
int extruder = ams.second->GetExtruderId() ? 0 : 0x10000; // Main (first) extruder at right
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": AMS unit %1% extruder %2% trays %3%")
% ams_id % ams.second->GetExtruderId() % ams.second->GetTrays().size();
// Bambu units are four slots wide; a generic unit reports its own lane count and its trays
// carry a cumulative lane index. The AMS map is keyed by string id, so count preceding units
// numerically to keep this the same index the parser and the agent use.
int generic_tray_base = 0;
if (!is_bbl_vendor) {
for (const auto& other : list) {
if (std::stoi(other.first) < ams_id)
generic_tray_base += static_cast<int>(other.second->GetTrays().size());
}
}
size_t inserted_trays = 0;
size_t rejected_trays = 0;
for (auto tray : ams.second->GetTrays()) {
int slot_id = std::stoi(tray.first);
filament_ams_list.emplace(extruder + (ams_id * 4 + slot_id),
build_tray_config(*tray.second, get_ams_name(ams_id, slot_id), std::to_string(ams_id), std::to_string(slot_id)));
const int tray_index = is_bbl_vendor ? ams_id * 4 + slot_id : generic_tray_base + slot_id;
const int key = extruder + tray_index;
auto inserted = filament_ams_list.emplace(key,
build_tray_config(*tray.second, get_ams_name(ams_id, slot_id), std::to_string(ams_id), std::to_string(slot_id),
ams.second->GetExtruderId()));
if (inserted.second) {
++inserted_trays;
} else {
++rejected_trays;
const auto &existing = inserted.first->second;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": AMS insert collision key %1% incoming_ams %2% incoming_slot %3% incoming_extruder %4% existing_origin %5% existing_ams %6% existing_slot %7% existing_extruder %8% existing_type %9% existing_id %10%")
% key % ams_id % slot_id % ams.second->GetExtruderId()
% (virtual_slot_keys.count(key) ? "virtual" : "ams")
% existing.opt_string("ams_id", 0u)
% existing.opt_string("slot_id", 0u)
% (existing.has("device_tray_extruder_id") ? existing.opt_int("device_tray_extruder_id") : -1)
% existing.opt_string("filament_type", 0u)
% existing.opt_string("filament_id", 0u);
}
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": AMS unit %1% tray inserts accepted %2% rejected %3%")
% ams_id % inserted_trays % rejected_trays;
}
// Diagnostic: dump the final output map (origin, key, identity/data flags) so parser output can
// be correlated with the entries logged at the sync boundary.
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": output entries %1%") % filament_ams_list.size();
for (const auto &entry : filament_ams_list) {
const auto &cfg = entry.second;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": out %1% key %2% ams_id %3% slot_id %4% extruder %5% type %6% id %7% exist %8% placeholder %9%")
% (virtual_slot_keys.count(entry.first) ? "virtual" : "ams")
% entry.first
% cfg.opt_string("ams_id", 0u)
% cfg.opt_string("slot_id", 0u)
% (cfg.has("device_tray_extruder_id") ? cfg.opt_int("device_tray_extruder_id") : -1)
% cfg.opt_string("filament_type", 0u)
% cfg.opt_string("filament_id", 0u)
% cfg.opt_bool("filament_exist", 0u)
% cfg.opt_bool("filament_slot_placeholder", 0u);
}
return filament_ams_list;
}
@@ -6381,6 +6533,20 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
std::vector<std::pair<DynamicPrintConfig *,std::string>> unknowns;
auto enable_append = wxGetApp().app_config->get_bool("enable_append_color_by_sync_ams");
auto sync_color_only = wxGetApp().app_config->get("sync_ams_filament_mode") == "1";
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": sync_color_only %1% enable_append %2% direct_sync %3% effective_use_map %4% filament_ams_list %5%")
% sync_color_only % enable_append % sync_result.direct_sync % (!sync_result.direct_sync) % list.size();
for (const auto &entry : list) {
const auto &tray = entry.second;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": tray %1% ams_id %2% slot_id %3% filament_id %4% type %5% color %6% filament_exist %7% placeholder %8%")
% entry.first
% tray.opt_string("ams_id", 0u)
% tray.opt_string("slot_id", 0u)
% tray.opt_string("filament_id", 0u)
% tray.opt_string("filament_type", 0u)
% tray.opt_string("filament_colour", 0u)
% (tray.has("filament_exist") && tray.opt_bool("filament_exist", 0u))
% (tray.has("filament_slot_placeholder") && tray.opt_bool("filament_slot_placeholder", 0u));
}
auto n = wxGetApp().preset_bundle->sync_ams_list(unknowns, !sync_result.direct_sync, sync_result.sync_maps, enable_append, merge_info, sync_color_only);
wxString detail;
for (auto & uk : unknowns) {
+1 -3
View File
@@ -2225,10 +2225,8 @@ void PublishSettingsDialog::refresh_tab_indicators()
if (section.variant_switch != nullptr) {
const wxString& title = m_categories[section.categories[i]].title;
const wxString text = on ? title + wxString(" ") + wxString(wxUniChar(0x2022)) : title;
if (section.variant_switch->GetOptionText(static_cast<unsigned int>(i)) != text) {
if (section.variant_switch->GetOptionText(static_cast<unsigned int>(i)) != text)
section.variant_switch->SetOptionText(static_cast<unsigned int>(i), text);
section.page->Layout();
}
}
any = any || on;
}
+8 -2
View File
@@ -37,6 +37,7 @@
#include "BackgroundSlicingProcess.hpp" // complete type for background_process().get_current_gcode_result()
#include "DeviceCore/DevStorage.h"
#include <wx/app.h>
#include <wx/event.h>
#include <string>
#include "libslic3r/PrintConfig.hpp"
@@ -1155,7 +1156,7 @@ void SelectMachineDialog::sync_ams_mapping_result(std::vector<FilamentInfo> &res
wxString ams_id;
wxColour ams_col;
if (f->tray_id == VIRTUAL_TRAY_MAIN_ID || f->tray_id == VIRTUAL_TRAY_DEPUTY_ID)
if (devPrinterUtil::IsVirtualSlot(f->ams_id))
{
ams_id = _L("Ext");
}else if (f->tray_id >= 0) {
@@ -1347,6 +1348,8 @@ bool SelectMachineDialog::get_ams_mapping_result(std::string &mapping_array_str,
if (m_ams_mapping_result.empty())
return false;
const bool is_bbl_vendor = wxTheApp != nullptr && wxGetApp().preset_bundle != nullptr && wxGetApp().preset_bundle->is_bbl_vendor();
bool valid_mapping_result = true;
int invalid_count = 0;
for (int i = 0; i < m_ams_mapping_result.size(); i++) {
@@ -1402,7 +1405,10 @@ bool SelectMachineDialog::get_ams_mapping_result(std::string &mapping_array_str,
// convert #RRGGBB to RRGGBBAA
mapping_item["sourceColor"] = m_filaments[k].color;
mapping_item["targetColor"] = m_ams_mapping_result[k].color;
if (tray_id == VIRTUAL_TRAY_MAIN_ID || tray_id == VIRTUAL_TRAY_DEPUTY_ID) { tray_id = -1; }
// The legacy v0 mapping array encodes an external spool as -1, a BBL-firmware
// contract. A generic printer keeps the virtual tray id (255 - extruder) so
// the payload still says which extruder's external slot was mapped.
if (is_bbl_vendor && (tray_id == VIRTUAL_TRAY_MAIN_ID || tray_id == VIRTUAL_TRAY_DEPUTY_ID)) { tray_id = -1; }
/*new ams mapping data*/
try {
@@ -1,63 +0,0 @@
#pragma once
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/Polygon.hpp"
#include <algorithm>
#include <cstddef>
#include <map>
#include <utility>
#include <vector>
namespace Slic3r::GUI {
struct SequentialClearanceInstance
{
double instance_height;
BoundingBox bounding_box;
Polygon hull_polygon;
ObjectID instance_id;
};
// PrintApply may group rotated copies in a different order from ModelObject::instances.
// Reorder one object's instances only when validation has assigned all of them an order.
inline void sort_sequential_clearance_instances(
std::vector<SequentialClearanceInstance>::iterator first, std::vector<SequentialClearanceInstance>::iterator last,
const std::map<ObjectID, int>& print_order)
{
if (!std::all_of(first, last, [&print_order](const SequentialClearanceInstance& instance) {
const auto order = print_order.find(instance.instance_id);
return order != print_order.end() && order->second > 0;
}))
return;
std::stable_sort(first, last, [&print_order](const SequentialClearanceInstance& lhs, const SequentialClearanceInstance& rhs) {
return print_order.at(lhs.instance_id) < print_order.at(rhs.instance_id);
});
}
// Input follows object-list order with validated print order within each object.
inline std::vector<std::pair<Polygon, float>> sequential_clearance_height_polygons(
const std::vector<SequentialClearanceInstance>& instances, double printable_height, double height_to_lid, double height_to_rod)
{
std::vector<std::pair<Polygon, float>> height_polygons;
height_polygons.reserve(instances.size());
for (size_t k = 0; k < instances.size(); ++k) {
const auto& instance = instances[k];
double height = k + 1 == instances.size() ? printable_height : height_to_lid;
for (size_t i = k + 1; i < instances.size(); ++i) {
const auto& next_bbox = instances[i].bounding_box;
if (std::min(instance.bounding_box.max.y(), next_bbox.max.y()) >
std::max(instance.bounding_box.min.y(), next_bbox.min.y())) {
height = height_to_rod;
break;
}
}
if (height < instance.instance_height)
height_polygons.emplace_back(instance.hull_polygon, float(height));
}
return height_polygons;
}
} // namespace Slic3r::GUI

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