mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-15 20:01:31 +00:00
Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6cf18fb96 | ||
|
|
4ee1b9c6e9 | ||
|
|
bc8d0a7858 | ||
|
|
e0651ec31d | ||
|
|
0caefcf4aa | ||
|
|
86905a8a20 | ||
|
|
d4fabbf7c8 | ||
|
|
51efb24752 | ||
|
|
cf9ec18df5 | ||
|
|
99de46b9d0 | ||
|
|
258f927a0f | ||
|
|
b8d40ed61f | ||
|
|
6aa613ccf0 | ||
|
|
54e6df0235 | ||
|
|
7599868797 | ||
|
|
81ec931990 | ||
|
|
8fd97b5120 | ||
|
|
aacc36b9d2 | ||
|
|
79ed246d26 | ||
|
|
92b5301bf6 | ||
|
|
20c157b0f4 | ||
|
|
4cf16febf6 | ||
|
|
70e52a105f | ||
|
|
b525cb79e1 | ||
|
|
86ab598899 |
@@ -569,12 +569,18 @@ on the **first** call. So:
|
||||
#endif
|
||||
```
|
||||
|
||||
**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()`
|
||||
**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()`
|
||||
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 rule (c12912e0df) are in
|
||||
WAYLAND_DISPLAY set"). The details and the WebKit rules (c12912e0df) are in
|
||||
`references/webview-gl-aui-media.md` §WebKitGTK on Linux sessions.
|
||||
|
||||
**wx's own detectors**, for reference:
|
||||
|
||||
@@ -61,7 +61,8 @@ available; GTK2/WebKit1 is an opt-out configuration the GUI does not support.
|
||||
11. A web host that re-themes in place handles `EVT_WEBVIEW_RECREATED` without `Skip()`; one that
|
||||
needs a reload lets it through. §[Orca wrapper](#orca-webview-wrapper-widgetswebview)
|
||||
12. Keep `WebViewWebKit`'s destructor removing the `"wx"` handler. §[Orca wrapper](#orca-webview-wrapper-widgetswebview)
|
||||
13. Do not widen `WEBKIT_DISABLE_COMPOSITING_MODE` beyond Orca's default-path XWayland case, and never
|
||||
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
|
||||
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
|
||||
@@ -567,33 +568,69 @@ 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)
|
||||
branches on `GDK_BACKEND`:
|
||||
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.
|
||||
- **Default path** (`GDK_BACKEND` unset or not starting with `x11`): if wx lacks EGL (`!wxHAS_EGL`) and
|
||||
`WAYLAND_DISPLAY` is set, it forces `GDK_BACKEND=x11`; it sets `WEBKIT_DISABLE_COMPOSITING_MODE=1`
|
||||
non-replacing **only** when 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.
|
||||
`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.
|
||||
- **`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 (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).
|
||||
compositing, which 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:** 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
|
||||
- **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
|
||||
waits forever for a C++→JS call.
|
||||
**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.
|
||||
**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.
|
||||
```cpp
|
||||
// Wrong: unconditional, kills HW acceleration everywhere
|
||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", false);
|
||||
// Right (default path only): non-replacing, before GTK init, Wayland session with XWayland
|
||||
if (display && *display && wayland && *wayland)
|
||||
// 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)
|
||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /*replace*/ false);
|
||||
```
|
||||
Cite: c12912e0df (`src/OrcaSlicer.cpp`; `resources/web/guide/0/load.js` `OnInit`
|
||||
|
||||
+15
-1
@@ -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: boost ccache hit rate by disabling precompiled headers (default: ON)"
|
||||
echo " -p: disable precompiled headers (on by default): more ccache hits after header changes, slower misses"
|
||||
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,6 +549,20 @@ 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
|
||||
|
||||
Vendored
+2
-17
@@ -1,22 +1,7 @@
|
||||
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.0.1.zip
|
||||
URL_HASH SHA256=d6d99e693366bc03897677650e8b2dfa76b5d6c54e2c9e70c03f0af821b0a52f
|
||||
DEPENDS ${ZLIB_PKG}
|
||||
URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.2.0.zip
|
||||
URL_HASH SHA256=1b4c2cb6cba1ca5e53b9e85eeeab02154cab4b70894c96e97a9c747a26d19d63
|
||||
CMAKE_ARGS
|
||||
-DENABLE_SHARED=OFF
|
||||
-DENABLE_STATIC=ON
|
||||
${jpeg_flag}
|
||||
)
|
||||
|
||||
+1336
-56
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
+4922
-1909
File diff suppressed because it is too large
Load Diff
+6162
-2709
File diff suppressed because it is too large
Load Diff
@@ -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="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>
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 415 B |
@@ -592,6 +592,10 @@
|
||||
"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"
|
||||
@@ -1096,6 +1100,10 @@
|
||||
"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"
|
||||
@@ -2260,6 +2268,10 @@
|
||||
"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"
|
||||
@@ -3812,6 +3824,10 @@
|
||||
"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"
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"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,6 +1,6 @@
|
||||
{
|
||||
"name": "RatRig",
|
||||
"version": "02.04.00.06",
|
||||
"version": "02.04.00.07",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] 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] 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]}",
|
||||
"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] BED_TEMP=[bed_temperature_initial_layer_single]",
|
||||
"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_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()">
|
||||
|
||||
@@ -156,12 +156,16 @@ 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.0.1
|
||||
# libjpeg-turbo 3.2.0
|
||||
- type: file
|
||||
url: https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.0.1.zip
|
||||
sha256: d6d99e693366bc03897677650e8b2dfa76b5d6c54e2c9e70c03f0af821b0a52f
|
||||
url: https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.2.0.zip
|
||||
sha256: 1b4c2cb6cba1ca5e53b9e85eeeab02154cab4b70894c96e97a9c747a26d19d63
|
||||
dest: external-packages/JPEG
|
||||
|
||||
# Freetype 2.12.1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# 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
|
||||
# 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://github.com/bambulab/BambuStudio/issues/3440
|
||||
# Use LC_NUMERIC instead of LC_ALL to prevent decimal separator issues
|
||||
|
||||
+21
-15
@@ -163,6 +163,7 @@ using json = nlohmann::json;
|
||||
#include <X11/Xlib.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include "slic3r/GUI/Widgets/WebView.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
@@ -1156,8 +1157,10 @@ 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 still applies it on
|
||||
// XWayland sessions as a conservative fallback for older WebKit.
|
||||
// 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.
|
||||
// ------------------------------------------------------------------
|
||||
{
|
||||
const char* gdk_backend = ::getenv("GDK_BACKEND");
|
||||
@@ -1165,6 +1168,14 @@ 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.";
|
||||
@@ -1204,23 +1215,18 @@ 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,7 +429,8 @@ 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
|
||||
export WEBKIT_DISABLE_DMABUF_RENDERER=1
|
||||
# WebKit's NVIDIA DMABUF workaround is applied by the binary itself,
|
||||
# gated on the runtime WebKitGTK version (src/OrcaSlicer.cpp).
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -858,11 +858,10 @@ void PrintObject::contour_z()
|
||||
BOOST_LOG_TRIVIAL(debug) << "Contouring in parallel - start";
|
||||
|
||||
TriangleMesh mesh = this->m_model_object->raw_mesh();
|
||||
if (m_model_object->instances.size() != 1) {
|
||||
throw RuntimeError("ContourZ: unexpected number of instances");
|
||||
}
|
||||
|
||||
ModelInstance *inst = m_model_object->instances.front();
|
||||
// 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;
|
||||
Point center_offset = this->center_offset();
|
||||
Geometry::Transformation trans = inst->get_transformation();
|
||||
|
||||
|
||||
@@ -689,6 +689,8 @@ 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
|
||||
@@ -712,6 +714,9 @@ 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
|
||||
|
||||
@@ -85,6 +85,96 @@ 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))
|
||||
@@ -740,8 +830,19 @@ 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;
|
||||
char col_buf[10];
|
||||
sprintf(col_buf, "%02X%02X%02X%02X", (int)color.Red(), (int)color.Green(), (int)color.Blue(), (int)color.Alpha());
|
||||
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;
|
||||
}
|
||||
|
||||
if (ams_filament_id.empty() || nozzle_temp_min.empty() || nozzle_temp_max.empty() || m_filament_type.empty()) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Invalid Setting id";
|
||||
@@ -760,13 +861,33 @@ 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 = " << col_buf
|
||||
<< ", tray_color = " << tray_color
|
||||
<< ", 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, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
//reset param
|
||||
@@ -923,8 +1044,15 @@ void AMSMaterialsSetting::set_ctype(int ctype)
|
||||
|
||||
void AMSMaterialsSetting::on_picker_color(wxCommandEvent& event)
|
||||
{
|
||||
unsigned int color_num = event.GetInt();
|
||||
set_color(wxColour(color_num>>24&0xFF, color_num>>16&0xFF, color_num>>8&0xFF, color_num&0xFF));
|
||||
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);
|
||||
}
|
||||
|
||||
void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
|
||||
@@ -939,14 +1067,12 @@ void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<wxColour> ams_colors;
|
||||
obj->GetFilaSystem()->CollectAmsColors(ams_colors);
|
||||
|
||||
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);
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1735,7 +1861,46 @@ 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 (alpha == 0) {
|
||||
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) {
|
||||
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;
|
||||
@@ -1777,52 +1942,6 @@ 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) {
|
||||
@@ -1891,19 +2010,21 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
|
||||
m_ams_fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
//other
|
||||
wxFlexGridSizer* fg_sizer;
|
||||
fg_sizer = new wxFlexGridSizer(0, 8, 0, 0);
|
||||
fg_sizer->SetFlexibleDirection(wxBOTH);
|
||||
fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
m_other_fg_sizer = new wxFlexGridSizer(0, 8, 0, 0);
|
||||
m_other_fg_sizer->SetFlexibleDirection(wxBOTH);
|
||||
m_other_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);
|
||||
fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
m_default_color_pickers.push_back(cp);
|
||||
m_other_fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
|
||||
set_def_colour(cp->m_colour);
|
||||
|
||||
@@ -1970,7 +2091,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(fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_other_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));
|
||||
@@ -1981,7 +2102,7 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
|
||||
m_def_color_box->Fit();
|
||||
|
||||
m_sizer_main->Add(m_def_color_box, 0, wxALL | wxEXPAND, 10);
|
||||
SetSizer(m_sizer_main);
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
@@ -2019,13 +2140,15 @@ void ColorPickerPopup::on_custom_clr_picker(wxMouseEvent& /*event*/)
|
||||
wxPostEvent(GetParent(), evt);
|
||||
}
|
||||
|
||||
void ColorPickerPopup::set_ams_colours(std::vector<wxColour> ams)
|
||||
void ColorPickerPopup::set_ams_colours(const std::vector<ColorItem>& 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);
|
||||
}
|
||||
@@ -2035,17 +2158,22 @@ void ColorPickerPopup::set_ams_colours(std::vector<wxColour> ams)
|
||||
}
|
||||
|
||||
|
||||
m_ams_colors = ams;
|
||||
for (wxColour col : m_ams_colors) {
|
||||
m_ams_color_items = ams;
|
||||
for (const ColorItem& item : m_ams_color_items) {
|
||||
if (item.colors.empty()) continue;
|
||||
|
||||
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
cp->set_color(col);
|
||||
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)));
|
||||
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);
|
||||
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);
|
||||
@@ -2058,9 +2186,68 @@ void ColorPickerPopup::set_ams_colours(std::vector<wxColour> ams)
|
||||
Fit();
|
||||
}
|
||||
|
||||
void ColorPickerPopup::set_def_colour(wxColour col)
|
||||
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)
|
||||
{
|
||||
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) {
|
||||
@@ -2069,7 +2256,8 @@ void ColorPickerPopup::set_def_colour(wxColour col)
|
||||
}
|
||||
|
||||
for (ColorPicker* cp : m_color_pickers) {
|
||||
if (cp->m_colour == m_def_col) {
|
||||
if (!cp->IsShown()) continue;
|
||||
if (cp->ctype == m_def_ctype && cp->m_cols == m_def_cols) {
|
||||
cp->set_selected(true);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -82,14 +82,22 @@ 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<wxColour> m_ams_colors;
|
||||
std::vector<ColorItem> m_ams_color_items;
|
||||
std::vector<ColorPicker*> m_color_pickers;
|
||||
std::vector<ColorPicker*> m_ams_color_pickers;
|
||||
|
||||
@@ -97,13 +105,20 @@ public:
|
||||
ColorPickerPopup(wxWindow* parent);
|
||||
~ColorPickerPopup() {};
|
||||
void on_custom_clr_picker(wxMouseEvent& event);
|
||||
void set_ams_colours(std::vector<wxColour> ams);
|
||||
void set_def_colour(wxColour col);
|
||||
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 paintEvent(wxPaintEvent& evt);
|
||||
virtual void OnDismiss() wxOVERRIDE;
|
||||
virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE;
|
||||
|
||||
public:
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -747,7 +747,9 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model& model, const Dyn
|
||||
|
||||
// Orca: prevent resetting under gcode viewer mode
|
||||
if (invalidated != PrintBase::APPLY_STATUS_UNCHANGED) {
|
||||
const auto plater = GUI::wxGetApp().mainframe->m_plater;
|
||||
// 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();
|
||||
if (plater && plater->only_gcode_mode()) {
|
||||
invalidated = PrintBase::APPLY_STATUS_UNCHANGED;
|
||||
}
|
||||
|
||||
@@ -1798,7 +1798,8 @@ 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)
|
||||
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 tag_tray_id = 0;
|
||||
int tag_ams_id = ams_id;
|
||||
@@ -1827,6 +1828,10 @@ 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);
|
||||
}
|
||||
@@ -5478,6 +5483,7 @@ 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;
|
||||
|
||||
@@ -674,6 +674,7 @@ 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};
|
||||
@@ -804,7 +805,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);
|
||||
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_select_tray(std::string tray_id);
|
||||
int command_ams_refresh_rfid(int ams_id, int slot_id);
|
||||
int command_ams_control(std::string action);
|
||||
|
||||
@@ -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.m_colors.emplace(wxColour(color_str));
|
||||
fila_color.AddColor(wxColour(color_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,17 @@ 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)
|
||||
{
|
||||
|
||||
+108
-15
@@ -252,9 +252,11 @@
|
||||
#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>
|
||||
@@ -333,6 +335,23 @@ 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;
|
||||
@@ -3063,6 +3082,37 @@ 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()
|
||||
@@ -5434,12 +5484,7 @@ std::string GUI_App::handle_web_request(std::string cmd)
|
||||
this->request_open_project({});
|
||||
}
|
||||
else if (command_str.compare("homepage_open_cloud") == 0) {
|
||||
// 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());
|
||||
});
|
||||
this->open_orca_cloud();
|
||||
}
|
||||
else if (command_str.compare("homepage_close_cloud") == 0) {
|
||||
CallAfter([]() {
|
||||
@@ -5588,6 +5633,22 @@ 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 {
|
||||
@@ -5865,7 +5926,7 @@ void GUI_App::on_update_machine_list(wxCommandEvent &evt)
|
||||
}*/
|
||||
}
|
||||
|
||||
wxString GUI_App::cloud_webview_handoff()
|
||||
wxString GUI_App::cloud_webview_handoff(const std::string& next)
|
||||
{
|
||||
// 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
|
||||
@@ -5873,7 +5934,7 @@ wxString GUI_App::cloud_webview_handoff()
|
||||
auto orca = get_orca_cloud_agent(m_agent);
|
||||
if (!orca)
|
||||
return {};
|
||||
return from_u8(orca->build_web_session_start_url());
|
||||
return from_u8(orca->build_web_session_start_url(next));
|
||||
}
|
||||
|
||||
void GUI_App::request_cloud_web_session(const std::string& state)
|
||||
@@ -6016,7 +6077,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());
|
||||
home->set_cloud_mode(true, cloud_webview_handoff(m_cloud_open_path));
|
||||
}
|
||||
|
||||
|
||||
@@ -6863,7 +6924,7 @@ void GUI_App::push_notification(const MachineObject* obj, wxString msg, wxStrin
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::reload_settings()
|
||||
bool GUI_App::reload_settings()
|
||||
{
|
||||
if (preset_bundle && m_agent) {
|
||||
// Load user's personal presets
|
||||
@@ -6871,7 +6932,7 @@ void 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;
|
||||
return false;
|
||||
}
|
||||
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 {
|
||||
@@ -6946,13 +7007,30 @@ void 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)
|
||||
@@ -6997,6 +7075,9 @@ 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) {
|
||||
@@ -7763,6 +7844,7 @@ 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).
|
||||
@@ -7836,8 +7918,16 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
|
||||
finishFn(ret == 0);
|
||||
|
||||
if (ret == 0 && m_agent && !t.expired())
|
||||
reload_settings();
|
||||
// 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);
|
||||
});
|
||||
|
||||
// For orca specific syncing
|
||||
auto orca_agent = std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent());
|
||||
@@ -7852,8 +7942,11 @@ 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 asked for it.
|
||||
if (tick_tock % 120 == 0 || m_sync_user_presets_now.exchange(false, std::memory_order_acq_rel)) {
|
||||
// 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) {
|
||||
tick_tock = 0;
|
||||
if (m_agent) {
|
||||
if (!m_agent->is_user_login()) {
|
||||
|
||||
@@ -355,6 +355,9 @@ 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 };
|
||||
@@ -365,6 +368,8 @@ 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;
|
||||
@@ -466,6 +471,9 @@ 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();
|
||||
@@ -572,7 +580,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();
|
||||
wxString cloud_webview_handoff(const std::string& next = std::string());
|
||||
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);
|
||||
@@ -589,7 +597,8 @@ 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; by_user also reports when there is nothing to migrate.
|
||||
// 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.
|
||||
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);
|
||||
@@ -600,7 +609,8 @@ 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);
|
||||
void reload_settings();
|
||||
// Returns false when the cloud presets could not be fetched, so nothing was reloaded.
|
||||
bool reload_settings();
|
||||
void remove_user_presets();
|
||||
|
||||
bool maybe_migrate_user_presets_on_login();
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
#include "GLGizmoUtils.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
@@ -72,6 +73,7 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
@@ -97,6 +99,48 @@ 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.
|
||||
@@ -2919,8 +2963,9 @@ void GLGizmoTextureDisplacement::run_uv_command(int cmd, float value)
|
||||
}
|
||||
else if (cmd == int(Command::PickTexture)) {
|
||||
// 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_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;
|
||||
@@ -4102,7 +4147,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.is_user)
|
||||
if (entry.source == TextureSource::User)
|
||||
touch_user_texture(entry.path);
|
||||
|
||||
rebuild_preview();
|
||||
@@ -4129,6 +4174,40 @@ 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();
|
||||
@@ -4145,17 +4224,55 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
|
||||
ImGui::OpenPopup(popup_id);
|
||||
}
|
||||
|
||||
const float tile = std::round(m_imgui->scaled(3.2f));
|
||||
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 tile = std::round(m_imgui->scaled(3.2f));
|
||||
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 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))
|
||||
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)
|
||||
return;
|
||||
if (layer == nullptr) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
@@ -4171,16 +4288,49 @@ 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);
|
||||
|
||||
m_imgui->push_bold_font();
|
||||
m_imgui->text(_L("Choose a texture"));
|
||||
m_imgui->pop_bold_font();
|
||||
// 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();
|
||||
}
|
||||
|
||||
const std::vector<TextureLibraryEntry> &library = texture_library();
|
||||
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;
|
||||
// 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
|
||||
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
|
||||
@@ -4196,16 +4346,15 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
|
||||
ImGui::Dummy(ImVec2(grid_w, float(rows) * tile + float(rows - 1) * spacing));
|
||||
}
|
||||
};
|
||||
// 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) {
|
||||
// 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) {
|
||||
int n = 0;
|
||||
for (const TextureLibraryEntry &entry : library) {
|
||||
if (entry.is_user != user)
|
||||
continue;
|
||||
for (const TextureLibraryEntry *entry : entries) {
|
||||
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) {
|
||||
@@ -4223,15 +4372,27 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane
|
||||
} else {
|
||||
clicked = ImGui::Button("?", ImVec2(tile, tile));
|
||||
}
|
||||
if (user)
|
||||
if (removable)
|
||||
ImGui::SetItemAllowOverlap(); // the remove button drawn over its corner takes its own clicks
|
||||
const ImVec2 a = ImGui::GetItemRectMin(), b = ImGui::GetItemRectMax();
|
||||
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);
|
||||
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 (user && ImGui::IsMouseHoveringRect(a, b)) {
|
||||
// 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)) {
|
||||
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));
|
||||
@@ -4244,27 +4405,162 @@ 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;
|
||||
};
|
||||
|
||||
ImGui::TextDisabled("%s", _u8L("Built-in").c_str());
|
||||
// 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
|
||||
{
|
||||
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, false);
|
||||
const int count = grid(origin, built_in.entries, false);
|
||||
end_grid(origin, count);
|
||||
if (count == 0)
|
||||
ImGui::TextDisabled("%s", _u8L("No textures found.").c_str());
|
||||
}
|
||||
ImGui::TextDisabled("%s", _u8L("My textures").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;
|
||||
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, true);
|
||||
const int count = grid(origin, user.entries, true);
|
||||
ImGui::SetCursorScreenPos(tile_pos(origin, count));
|
||||
if (ImGui::Button("+##import_texture", ImVec2(tile, tile)))
|
||||
import_clicked = true;
|
||||
@@ -4273,17 +4569,25 @@ 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();
|
||||
|
||||
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();
|
||||
|
||||
if (chosen || import_clicked)
|
||||
if (chosen || import_clicked || close_clicked || download_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.
|
||||
@@ -6586,19 +6890,6 @@ 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) {
|
||||
@@ -6791,52 +7082,11 @@ 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;
|
||||
|
||||
// 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.
|
||||
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::BeginChild("##td_body", ImVec2(panel_w, body_h), false, ImGuiWindowFlags_NoScrollWithMouse);
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
ease_wheel_scroll(m_panel_scroll_target, m_panel_scroll_applied);
|
||||
|
||||
// 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,
|
||||
@@ -7081,8 +7331,9 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
||||
|
||||
// Opens the texture library for a layer, making it the active one too (after the loop).
|
||||
const auto open_picker = [&](int slot) {
|
||||
m_picker_slot = slot;
|
||||
m_picker_open_request = true;
|
||||
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) {
|
||||
@@ -8077,8 +8328,7 @@ 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();
|
||||
ImGui::PopStyleColor(5);
|
||||
ImGui::PopStyleVar(2);
|
||||
pop_slim_scrollbar_style();
|
||||
|
||||
// The layer-list changes deferred from the loop above.
|
||||
if (slot_to_remove >= 0)
|
||||
|
||||
@@ -428,6 +428,10 @@ 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);
|
||||
|
||||
@@ -1062,6 +1066,8 @@ 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
|
||||
@@ -1103,12 +1109,15 @@ private:
|
||||
// The config key behind m_undocked.
|
||||
static constexpr const char *UNDOCKED_CONFIG_KEY = "texture_displacement_panel_undocked";
|
||||
|
||||
// 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.
|
||||
// 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);
|
||||
float m_panel_scroll_target = 0.f;
|
||||
float m_panel_scroll_applied = -1.f; // what the easing wrote last frame; <0 until the first one
|
||||
float m_panel_scroll_applied = -1.f;
|
||||
// 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.
|
||||
|
||||
@@ -83,11 +83,13 @@ 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)
|
||||
ImGui::SetTooltip("%s\n%s", _u8L("Wiki Guide").c_str(), url);
|
||||
wxGetApp().imgui()->tooltip(_u8L("Wiki Guide") + "\n" + url, -1.f);
|
||||
else
|
||||
ImGui::SetTooltip("%s", _u8L("Wiki Guide").c_str());
|
||||
wxGetApp().imgui()->tooltip(_u8L("Wiki Guide"), -1.f);
|
||||
}
|
||||
return clicked; // for dynamically generated links
|
||||
}
|
||||
@@ -101,9 +103,9 @@ namespace Slic3r::GUI::GLGizmoUtils {
|
||||
wxLaunchDefaultBrowser(wxString::FromUTF8(url));
|
||||
if (ImGui::IsItemHovered()){
|
||||
if(url && *url)
|
||||
ImGui::SetTooltip("%s\n%s", _u8L("Video Guide").c_str(), url);
|
||||
wxGetApp().imgui()->tooltip(_u8L("Video Guide") + "\n" + url, -1.f);
|
||||
else
|
||||
ImGui::SetTooltip("%s", _u8L("Video Guide").c_str());
|
||||
wxGetApp().imgui()->tooltip(_u8L("Video Guide"), -1.f);
|
||||
}
|
||||
return clicked; // for dynamically generated links
|
||||
}
|
||||
|
||||
@@ -857,11 +857,12 @@ 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;
|
||||
@@ -964,11 +965,12 @@ 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;
|
||||
@@ -1377,11 +1379,12 @@ 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;
|
||||
|
||||
@@ -505,10 +505,12 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt)
|
||||
io.MouseDown[0] = evt.LeftIsDown();
|
||||
io.MouseDown[1] = evt.RightIsDown();
|
||||
io.MouseDown[2] = evt.MiddleIsDown();
|
||||
float wheel_delta = static_cast<float>(evt.GetWheelDelta());
|
||||
if (wheel_delta != 0.0f) {
|
||||
// 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)
|
||||
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;
|
||||
|
||||
|
||||
@@ -816,6 +816,18 @@ 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;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "TextureLibrary.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <cstddef>
|
||||
#include <boost/filesystem/directory.hpp>
|
||||
@@ -21,6 +22,7 @@
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <wx/app.h>
|
||||
#include <wx/gdicmn.h>
|
||||
#include <utility>
|
||||
#include <wx/image.h>
|
||||
@@ -36,6 +38,9 @@
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
#include "slic3r/plugin/PluginFsUtils.hpp"
|
||||
#include "slic3r/plugin/host/PluginResourceTextures.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
namespace {
|
||||
@@ -50,7 +55,7 @@ bool is_image_file(const boost::filesystem::path &path)
|
||||
return ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp";
|
||||
}
|
||||
|
||||
void scan_dir(const boost::filesystem::path &dir, bool is_user, std::vector<TextureLibraryEntry> &out)
|
||||
void scan_dir(const boost::filesystem::path &dir, TextureSource source, std::vector<TextureLibraryEntry> &out)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
if (!boost::filesystem::is_directory(dir, ec))
|
||||
@@ -60,12 +65,43 @@ void scan_dir(const boost::filesystem::path &dir, bool is_user, std::vector<Text
|
||||
for (boost::filesystem::directory_iterator it(dir, ec), end; it != end && !ec; it.increment(ec)) {
|
||||
if (!boost::filesystem::is_regular_file(it->path(), ec) || !is_image_file(it->path()))
|
||||
continue;
|
||||
out.push_back({ it->path().stem().string(), it->path().string(), is_user });
|
||||
out.push_back({ it->path().stem().string(), it->path().string(), source });
|
||||
}
|
||||
std::sort(out.begin() + first, out.end(),
|
||||
[](const TextureLibraryEntry &a, const TextureLibraryEntry &b) { return a.name < b.name; });
|
||||
}
|
||||
|
||||
// Appends every loaded pack's textures, ordered by the pack's display name and then by file name. Each
|
||||
// entry carries the contributing pack's display name, which the picker uses to group a pack's textures
|
||||
// into one section; a pack name is unique among loaded packs of this capability type, so the name both
|
||||
// groups the section and keeps its entries contiguous. A pack whose directory fails validation is
|
||||
// skipped, so one bad pack never hides the others.
|
||||
void scan_plugin_textures(std::vector<TextureLibraryEntry> &out)
|
||||
{
|
||||
std::vector<TextureLibraryEntry> found;
|
||||
for (const ResourceTextureEntry &pack : PluginResourceTextures::instance().entries()) {
|
||||
std::string error;
|
||||
const std::vector<std::string> files =
|
||||
collect_resource_texture_files(pack.texture_dir, pack.plugin_root, error);
|
||||
if (!error.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Texture pack '" << pack.display_name << "' was skipped: " << error;
|
||||
continue;
|
||||
}
|
||||
for (const std::string &file : files) {
|
||||
const boost::filesystem::path path(file);
|
||||
found.push_back({ path.stem().string(), file, TextureSource::Plugin, pack.display_name });
|
||||
}
|
||||
}
|
||||
|
||||
std::stable_sort(found.begin(), found.end(), [](const TextureLibraryEntry &a, const TextureLibraryEntry &b) {
|
||||
if (a.pack_name != b.pack_name)
|
||||
return a.pack_name < b.pack_name;
|
||||
return a.name < b.name;
|
||||
});
|
||||
for (TextureLibraryEntry &entry : found)
|
||||
out.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
// True if the image carries any colour at all, i.e. some pixel's channels are not all equal. A
|
||||
// photo saved as RGB but actually grey should still be stored as a grayscale PNG - it is a third of
|
||||
// the bytes, and the colour feature keys off has_color(), so storing a grey image as RGB would offer
|
||||
@@ -208,6 +244,11 @@ bool is_supported_height_map(const std::vector<unsigned char> &bytes)
|
||||
|
||||
std::vector<TextureLibraryEntry> g_library;
|
||||
bool g_library_scanned = false;
|
||||
// The generation g_library was last built from, so texture_library() can rescan after a pack change.
|
||||
uint64_t g_library_scan_generation = 0;
|
||||
|
||||
// Bumped by plugin callbacks, read by the GUI frame. Atomic so a read never races a write.
|
||||
std::atomic<uint64_t> g_library_generation{ 0 };
|
||||
|
||||
// Writes `bytes` into the user's own texture folder as `stem`.png, uniquified so an existing texture -
|
||||
// shipped-name collision, or a second edit of the same source - is never overwritten. Returns the new
|
||||
@@ -255,6 +296,8 @@ const size_t RECENT_MAX = 200;
|
||||
std::vector<std::string> recent_texture_names()
|
||||
{
|
||||
std::vector<std::string> names;
|
||||
if (wxTheApp == nullptr) // headless or shutting down: there is no app config to read
|
||||
return names;
|
||||
if (wxGetApp().app_config == nullptr)
|
||||
return names;
|
||||
const std::string value = wxGetApp().app_config->get(RECENT_SECTION, RECENT_KEY);
|
||||
@@ -269,6 +312,8 @@ std::vector<std::string> recent_texture_names()
|
||||
|
||||
void store_recent_texture_names(std::vector<std::string> names)
|
||||
{
|
||||
if (wxTheApp == nullptr) // headless or shutting down: there is no app config to write
|
||||
return;
|
||||
if (wxGetApp().app_config == nullptr)
|
||||
return;
|
||||
if (names.size() > RECENT_MAX)
|
||||
@@ -279,8 +324,9 @@ void store_recent_texture_names(std::vector<std::string> names)
|
||||
wxGetApp().app_config->set(RECENT_SECTION, RECENT_KEY, value);
|
||||
}
|
||||
|
||||
// Reorders the user's entries (the tail of `library`) by recent use. Stable, so textures never used keep
|
||||
// the name order scan_dir() gave them, after every used one.
|
||||
// Reorders the user's entries by recent use. Stable, so textures never used keep the name order
|
||||
// scan_dir() gave them, after every used one. Only the user's own range is touched: shipped and pack
|
||||
// entries must keep the order their scans produced.
|
||||
void sort_user_textures(std::vector<TextureLibraryEntry> &library)
|
||||
{
|
||||
const std::vector<std::string> names = recent_texture_names();
|
||||
@@ -291,8 +337,11 @@ void sort_user_textures(std::vector<TextureLibraryEntry> &library)
|
||||
const auto it = rank.find(boost::filesystem::path(e.path).filename().string());
|
||||
return it == rank.end() ? std::numeric_limits<int>::max() : it->second;
|
||||
};
|
||||
const auto first_user = std::find_if(library.begin(), library.end(), [](const TextureLibraryEntry &e) { return e.is_user; });
|
||||
std::stable_sort(first_user, library.end(),
|
||||
const auto first_user = std::find_if(library.begin(), library.end(),
|
||||
[](const TextureLibraryEntry &e) { return e.source == TextureSource::User; });
|
||||
const auto user_end = std::find_if(first_user, library.end(),
|
||||
[](const TextureLibraryEntry &e) { return e.source != TextureSource::User; });
|
||||
std::stable_sort(first_user, user_end,
|
||||
[&rank_of](const TextureLibraryEntry &a, const TextureLibraryEntry &b) { return rank_of(a) < rank_of(b); });
|
||||
}
|
||||
|
||||
@@ -311,6 +360,16 @@ bool in_user_texture_dir(const std::string &path)
|
||||
|
||||
} // namespace
|
||||
|
||||
uint64_t texture_library_generation()
|
||||
{
|
||||
return g_library_generation.load();
|
||||
}
|
||||
|
||||
void bump_texture_library_generation()
|
||||
{
|
||||
g_library_generation.fetch_add(1);
|
||||
}
|
||||
|
||||
std::string user_texture_dir()
|
||||
{
|
||||
const boost::filesystem::path dir = boost::filesystem::path(Slic3r::data_dir()) / "textures" / "displacement";
|
||||
@@ -325,17 +384,20 @@ std::string user_texture_dir()
|
||||
|
||||
const std::vector<TextureLibraryEntry> &texture_library(bool force_rescan)
|
||||
{
|
||||
if (g_library_scanned && !force_rescan)
|
||||
const uint64_t generation = texture_library_generation();
|
||||
if (g_library_scanned && !force_rescan && generation == g_library_scan_generation)
|
||||
return g_library;
|
||||
|
||||
g_library.clear();
|
||||
scan_dir(boost::filesystem::path(Slic3r::resources_dir()) / "textures" / "displacement", false, g_library);
|
||||
scan_dir(boost::filesystem::path(Slic3r::resources_dir()) / "textures" / "displacement", TextureSource::Shipped, g_library);
|
||||
const std::string user_dir = user_texture_dir();
|
||||
if (!user_dir.empty())
|
||||
scan_dir(boost::filesystem::path(user_dir), true, g_library);
|
||||
scan_dir(boost::filesystem::path(user_dir), TextureSource::User, g_library);
|
||||
sort_user_textures(g_library);
|
||||
scan_plugin_textures(g_library);
|
||||
|
||||
g_library_scanned = true;
|
||||
g_library_scan_generation = generation;
|
||||
g_library_scanned = true;
|
||||
return g_library;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef slic3r_TextureLibrary_hpp_
|
||||
#define slic3r_TextureLibrary_hpp_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -10,26 +11,45 @@
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
// One selectable height-map texture in the texture-displacement gizmo's texture picker.
|
||||
// Where a library texture came from. The picker lists each source under its own heading.
|
||||
enum class TextureSource { Shipped, User, Plugin };
|
||||
|
||||
// One selectable height-map texture in the texture-displacement gizmo's texture picker. A texture that
|
||||
// came from a plugin pack also carries the provenance the picker groups its sections by.
|
||||
struct TextureLibraryEntry
|
||||
{
|
||||
std::string name; // display name (the file's stem, e.g. "Wood Grain")
|
||||
std::string path; // absolute path on disk
|
||||
bool is_user; // imported by the user, as opposed to shipped with OrcaSlicer
|
||||
std::string name; // display name (the file's stem, e.g. "Wood Grain")
|
||||
std::string path; // absolute path on disk
|
||||
TextureSource source; // which of the three sources it came from
|
||||
|
||||
std::string pack_name; // contributing pack's display name; empty for Shipped and User
|
||||
};
|
||||
|
||||
// Every available height-map texture: the ones shipped in resources/textures/displacement first, sorted
|
||||
// by name, then the user's own from <data_dir>/textures/displacement - most recently used first (see
|
||||
// touch_user_texture()), then any never used, by name.
|
||||
// Every available height-map texture, in the order the picker lists them: the ones shipped in
|
||||
// resources/textures/displacement first, by name; then the user's own from <data_dir>/textures/displacement,
|
||||
// most recently used first (see touch_user_texture()) and the rest by name; then the loaded texture
|
||||
// packs, ordered by pack display name and then by file name.
|
||||
//
|
||||
// The two live in separate directories deliberately: an app update replaces the resources tree
|
||||
// wholesale, so anything the user imported has to sit somewhere that update can never overwrite or
|
||||
// delete. `is_user` is what the picker uses to show them under separate headings.
|
||||
// The shipped and user textures live in separate directories deliberately: an app update replaces the
|
||||
// resources tree wholesale, so anything the user imported has to sit somewhere that update can never
|
||||
// overwrite or delete. Pack textures are read-only like the shipped ones. Every texture carries its
|
||||
// `source`; a pack texture also carries `pack_name`, and the picker groups pack textures into one
|
||||
// collapsible section per pack name. A pack's name is unique among loaded packs of this capability type -
|
||||
// the loader refuses a collision with "Capability collision for type ... and name ...", see
|
||||
// PluginManager::check_registry_locked (PluginManager.cpp:211-225) - so the name on its own identifies a
|
||||
// section and keeps that pack's entries contiguous.
|
||||
//
|
||||
// Scanned once and cached. Pass force_rescan after an import, or to pick up a file the user dropped
|
||||
// into either folder by hand while the app was running.
|
||||
// into either folder by hand while the app was running. A rescan also happens on its own when the pack
|
||||
// registry changes, which is what texture_library_generation() tracks.
|
||||
const std::vector<TextureLibraryEntry> &texture_library(bool force_rescan = false);
|
||||
|
||||
// Monotonic counter, bumped whenever the set of loaded texture packs changes. The GUI reads it to
|
||||
// decide whether texture_library() must rescan, and plugin callbacks bump it. Safe to read from any
|
||||
// thread at any time.
|
||||
uint64_t texture_library_generation();
|
||||
void bump_texture_library_generation();
|
||||
|
||||
// Moves one of the user's own textures to the front of the recently-used order texture_library() lists
|
||||
// them in, and stores that order in the app config so it survives a restart. No-op for anything that is
|
||||
// not in user_texture_dir().
|
||||
|
||||
@@ -537,7 +537,7 @@ void WebViewPanel::cloud_signed_out(const wxString& signout_url)
|
||||
}
|
||||
}
|
||||
|
||||
void WebViewPanel::set_cloud_mode(bool on, const wxString& url)
|
||||
void WebViewPanel::set_cloud_mode(bool on, const wxString& url, const wxString& page_url)
|
||||
{
|
||||
if (on) {
|
||||
// load_cloud_url records the origin from whatever it loads, including the plain cloud
|
||||
@@ -546,6 +546,10 @@ void WebViewPanel::set_cloud_mode(bool on, const wxString& url)
|
||||
create_cloud_browser();
|
||||
m_cloud_seed_url = url;
|
||||
schedule_cloud_load(url.empty() ? wxString(kCloudUrl) : url);
|
||||
} else if (!page_url.empty()) {
|
||||
// The pane already holds a session, so an explicit page loads straight in; the handoff is
|
||||
// only needed to establish one, not to reach an ordinary page.
|
||||
load_cloud_url(page_url);
|
||||
} else if (!url.empty() && url != m_cloud_seed_url) {
|
||||
// Re-seed only when the Start URL actually changed — a login, an account switch or a
|
||||
// sign-out changes it; an ordinary re-open does not (guide §8). Reloading an unchanged
|
||||
@@ -556,6 +560,10 @@ void WebViewPanel::set_cloud_mode(bool on, const wxString& url)
|
||||
load_cloud_after_signout(url);
|
||||
}
|
||||
m_cloud_browser->Show();
|
||||
// The homepage shell owns the nav highlight and the cloud-mode layout class. A native open
|
||||
// (not the page's own GotoCloud) must ask it to switch; the page's SetCloudMode no-ops if it
|
||||
// already did. OnDocumentLoaded re-applies this when the homepage was still loading.
|
||||
RunScript("SetCloudMode(true)");
|
||||
} else if (m_cloud_browser) {
|
||||
// Closing keeps the pane alive with its session, so re-opening does not have to sign in
|
||||
// again (guide §10). Nothing is cleared here.
|
||||
|
||||
@@ -118,7 +118,10 @@ public:
|
||||
// Shows/hides the native OrcaCloud pane beside this homepage nav column. `url` is the
|
||||
// cloud Start URL ({web}/auth/handoff?next=…&user_id=…), which the page uses to decide
|
||||
// whether it already has a session; empty falls back to the plain cloud page.
|
||||
void set_cloud_mode(bool on, const wxString& url = wxString());
|
||||
// `page_url` is a full {web} page URL: an already-open pane is navigated straight there,
|
||||
// without re-running the handoff, which is what an explicit "open this page" wants. Empty
|
||||
// keeps whatever page the pane holds.
|
||||
void set_cloud_mode(bool on, const wxString& url = wxString(), const wxString& page_url = wxString());
|
||||
|
||||
// True while the OrcaCloud pane exists and is visible.
|
||||
bool cloud_mode_active() const { return m_cloud_browser && m_cloud_browser->IsShown(); }
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
#include <cstddef>
|
||||
#include <wx/simplebook.h>
|
||||
#include <wx/dcgraph.h>
|
||||
#include <wx/graphics.h>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <wx/timer.h>
|
||||
@@ -1409,22 +1411,17 @@ void AMSLib::render_lite_lib(wxDC& dc)
|
||||
|
||||
dc.SetPen(wxPen(*wxTRANSPARENT_PEN));
|
||||
if (m_info.material_cols.size() > 1) {
|
||||
int left = FromDIP(10);
|
||||
int gwidth = std::round(libsize.x / (m_info.material_cols.size() - 1));
|
||||
//gradient
|
||||
wxRect color_rect(FromDIP(10), FromDIP(10), libsize.x - FromDIP(18), libsize.y - FromDIP(18));
|
||||
if (m_info.ctype == 0) {
|
||||
for (int i = 0; i < m_info.material_cols.size() - 1; i++) {
|
||||
auto rect = wxRect(left, FromDIP(10), libsize.x - FromDIP(18), libsize.y - FromDIP(18));
|
||||
fill_gradient_rect_east(dc, rect, m_info.material_cols[i], m_info.material_cols[i + 1]);
|
||||
left += gwidth;
|
||||
}
|
||||
fill_gradient_rect_east(dc, color_rect, m_info.material_cols.front(), m_info.material_cols.back());
|
||||
}
|
||||
else {
|
||||
int cols_size = m_info.material_cols.size();
|
||||
for (int i = 0; i < cols_size; i++) {
|
||||
dc.SetBrush(wxBrush(m_info.material_cols[i]));
|
||||
float x = FromDIP(10) + ((float)libsize.x - FromDIP(18)) * i / cols_size;
|
||||
dc.DrawRoundedRectangle(x, FromDIP(10), ((float)libsize.x - FromDIP(17)) / cols_size, libsize.y - FromDIP(20), 0);
|
||||
int x = color_rect.x + color_rect.width * i / cols_size;
|
||||
int next_x = color_rect.x + color_rect.width * (i + 1) / cols_size;
|
||||
dc.DrawRectangle(x, color_rect.y, next_x - x, color_rect.height);
|
||||
}
|
||||
dc.SetBrush(wxBrush(tmp_lib_colour));
|
||||
}
|
||||
@@ -1516,7 +1513,27 @@ void AMSLib::render_generic_lib(wxDC &dc)
|
||||
int top = height - curr_height;
|
||||
|
||||
if (m_ams_model == EXT_AMS){
|
||||
dc.DrawRoundedRectangle(FromDIP(1), FromDIP(1), size.x - FromDIP(2), size.y - FromDIP(1), m_radius - 1);
|
||||
wxRect color_rect(FromDIP(1), FromDIP(1), size.x - FromDIP(2), size.y - FromDIP(1));
|
||||
if ((m_info.ctype == 0 || m_info.ctype == 1) && m_info.material_cols.size() > 1 && alpha != 0) {
|
||||
if (m_info.ctype == 0) {
|
||||
fill_gradient_rect_east(dc, color_rect, m_info.material_cols.front(), m_info.material_cols.back());
|
||||
}
|
||||
else {
|
||||
int cols_size = static_cast<int>(m_info.material_cols.size());
|
||||
for (int i = 0; i < cols_size; i++) {
|
||||
dc.SetPen(wxPen(*wxTRANSPARENT_PEN));
|
||||
dc.SetBrush(wxBrush(m_info.material_cols[i]));
|
||||
int x = color_rect.x + color_rect.width * i / cols_size;
|
||||
int next_x = color_rect.x + color_rect.width * (i + 1) / cols_size;
|
||||
dc.DrawRectangle(x, color_rect.y, next_x - x, color_rect.height);
|
||||
}
|
||||
}
|
||||
dc.SetPen(wxPen(*wxTRANSPARENT_PEN));
|
||||
dc.SetBrush(wxBrush(tmp_lib_colour));
|
||||
}
|
||||
else {
|
||||
dc.DrawRoundedRectangle(color_rect.x, color_rect.y, color_rect.width, color_rect.height, m_radius - 1);
|
||||
}
|
||||
if (alpha == 0) {
|
||||
dc.DrawBitmap(m_bitmap_transparent_def.bmp(), FromDIP(2), FromDIP(2));
|
||||
}
|
||||
@@ -2844,30 +2861,38 @@ void AMSPreview::doRender(wxDC &dc)
|
||||
//dc.DrawRoundedRectangle((size.x - rec_size.x) / 2, (size.y - rec_size.y) / 2, rec_size.x, rec_size.y, FromDIP(2));
|
||||
if (iter.material_cols.size() > 1)
|
||||
{
|
||||
int fleft = (size.x - AMS_ITEM_CUBE_SIZE.x) / 2;
|
||||
|
||||
float total_width = AMS_ITEM_CUBE_SIZE.x;
|
||||
int gwidth = (total_width / (iter.material_cols.size()));
|
||||
if (iter.ctype == 0) {
|
||||
for (int i = 0; i < iter.material_cols.size() - 1; i++) {
|
||||
|
||||
if ((fleft + gwidth) > (AMS_ITEM_CUBE_SIZE.x)) {
|
||||
gwidth = (fleft + AMS_ITEM_CUBE_SIZE.x) - fleft;
|
||||
}
|
||||
|
||||
auto rect = wxRect(fleft, (size.y - AMS_ITEM_CUBE_SIZE.y) / 2, gwidth, AMS_ITEM_CUBE_SIZE.y);
|
||||
fill_gradient_rect_east(dc, rect, iter.material_cols[i], iter.material_cols[i + 1]);
|
||||
fleft += gwidth;
|
||||
}
|
||||
const wxSize swatch_size = m_ams_item_type == AMSModel::N3S_AMS ? AMS_ITEM_CUBE_SIZE : AMS_ITEM_CUBE_SIZE2;
|
||||
const wxRect rect((size.x - swatch_size.x) / 2, (size.y - swatch_size.y) / 2, swatch_size.x, swatch_size.y);
|
||||
const int radius = m_ams_item_type == AMSModel::N3S_AMS ? 0 : FromDIP(3);
|
||||
std::unique_ptr<wxGraphicsContext> owned_gc;
|
||||
wxGraphicsContext* gc = dc.GetGraphicsContext();
|
||||
if (!gc) {
|
||||
owned_gc.reset(wxGraphicsContext::CreateFromUnknownDC(dc));
|
||||
gc = owned_gc.get();
|
||||
}
|
||||
else {
|
||||
int cols_size = iter.material_cols.size();
|
||||
for (int i = 0; i < cols_size; i++) {
|
||||
dc.SetPen(wxPen(*wxTRANSPARENT_PEN));
|
||||
dc.SetBrush(wxBrush(iter.material_cols[i]));
|
||||
float x = (size.x - AMS_ITEM_CUBE_SIZE.x) / 2 + total_width * i / cols_size;
|
||||
dc.DrawRectangle(x, (size.y - AMS_ITEM_CUBE_SIZE.y) / 2, total_width / cols_size, AMS_ITEM_CUBE_SIZE.y);
|
||||
if (gc) {
|
||||
wxGraphicsGradientStops stops(iter.material_cols.front(), iter.material_cols.back());
|
||||
const size_t count = iter.material_cols.size();
|
||||
if (iter.ctype == 0) {
|
||||
for (size_t i = 1; i + 1 < count; ++i)
|
||||
stops.Add(iter.material_cols[i], static_cast<float>(i) / (count - 1));
|
||||
} else {
|
||||
// Two stops at each boundary keep multicolour bands distinct.
|
||||
for (size_t i = 1; i < count; ++i) {
|
||||
const float position = static_cast<float>(i) / count;
|
||||
stops.Add(iter.material_cols[i - 1], position);
|
||||
stops.Add(iter.material_cols[i], position);
|
||||
}
|
||||
}
|
||||
gc->PushState();
|
||||
gc->SetPen(*wxTRANSPARENT_PEN);
|
||||
gc->SetBrush(gc->CreateLinearGradientBrush(rect.x, rect.y, rect.x + rect.width, rect.y, stops));
|
||||
gc->DrawRoundedRectangle(rect.x, rect.y, rect.width, rect.height, radius);
|
||||
gc->PopState();
|
||||
} else {
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(iter.material_colour);
|
||||
dc.DrawRoundedRectangle(rect, radius);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -74,6 +74,10 @@ webkit_web_view_run_javascript_finish (WebKitWebView
|
||||
GError **error);
|
||||
WEBKIT_API void
|
||||
webkit_javascript_result_unref (WebKitJavascriptResult *js_result);
|
||||
WEBKIT_API guint
|
||||
webkit_get_major_version (void);
|
||||
WEBKIT_API guint
|
||||
webkit_get_minor_version (void);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -427,6 +431,29 @@ bool WebView::NeedsRecreateOnShow()
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebView::NeedsCompositingWorkaround()
|
||||
{
|
||||
#ifdef __linux__
|
||||
const guint major = webkit_get_major_version();
|
||||
const guint minor = webkit_get_minor_version();
|
||||
return major < 2 || (major == 2 && minor < 42);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebView::SupportsForceSharedMemory()
|
||||
{
|
||||
#ifdef __linux__
|
||||
const guint major = webkit_get_major_version();
|
||||
const guint minor = webkit_get_minor_version();
|
||||
return major > 2 || (major == 2 && minor >= 44);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if wxUSE_WEBVIEW_EDGE
|
||||
bool WebView::CheckWebViewRuntime()
|
||||
{
|
||||
|
||||
@@ -34,6 +34,16 @@ public:
|
||||
// ignoring every navigation. A panel that gets true here recreates its view on first Show().
|
||||
static bool NeedsRecreateOnShow();
|
||||
|
||||
// True when the linked WebKitGTK predates 2.42, where accelerated compositing
|
||||
// could fail silently on XWayland sessions, so it is forced off as a fallback.
|
||||
// False on every other platform. Safe to call before GTK initialises.
|
||||
static bool NeedsCompositingWorkaround();
|
||||
|
||||
// True when the linked WebKitGTK is new enough (>= 2.44) to honour
|
||||
// WEBKIT_DMABUF_RENDERER_FORCE_SHM, the shared-memory transport switch.
|
||||
// False on every other platform. Safe to call before GTK initialises.
|
||||
static bool SupportsForceSharedMemory();
|
||||
|
||||
static void RecreateAll();
|
||||
};
|
||||
|
||||
|
||||
@@ -529,6 +529,7 @@ std::string PluginConfig::capability_type_label(PluginCapabilityType type)
|
||||
case PluginCapabilityType::Visualization: return _u8L("Visualization");
|
||||
case PluginCapabilityType::Script: return _u8L("Script");
|
||||
case PluginCapabilityType::SlicingPipeline: return _u8L("Slicing Pipeline");
|
||||
case PluginCapabilityType::ResourceTexture: return _u8L("Resource Texture");
|
||||
default: return _u8L("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <boost/filesystem/directory.hpp>
|
||||
#include <boost/filesystem/file_status.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#include "PluginAuditManager.hpp"
|
||||
@@ -1255,4 +1256,76 @@ boost::filesystem::path find_installed_plugin_entry(const boost::filesystem::pat
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> collect_resource_texture_files(const std::string& dir,
|
||||
const std::string& plugin_root,
|
||||
std::string& error)
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
// Picker sanity bound, not a security control: the popup draws every entry in its grid.
|
||||
constexpr size_t kMaxResourceTextureFiles = 64;
|
||||
|
||||
std::vector<std::string> files;
|
||||
error.clear();
|
||||
|
||||
if (dir.empty()) {
|
||||
error = _u8L("Resource texture directory is not set");
|
||||
return files;
|
||||
}
|
||||
if (plugin_root.empty()) {
|
||||
error = _u8L("Plugin folder could not be determined.");
|
||||
return files;
|
||||
}
|
||||
|
||||
boost::system::error_code ec;
|
||||
const fs::path resolved_dir = fs::weakly_canonical(fs::path(dir), ec);
|
||||
if (ec) {
|
||||
error = _u8L("Resource texture directory could not be resolved");
|
||||
return files;
|
||||
}
|
||||
|
||||
if (!fs::is_directory(resolved_dir, ec) || ec) {
|
||||
error = _u8L("Resource texture path is not a directory");
|
||||
return files;
|
||||
}
|
||||
|
||||
const fs::path resolved_root = fs::weakly_canonical(fs::path(plugin_root), ec);
|
||||
if (ec) {
|
||||
error = _u8L("Plugin folder could not be resolved");
|
||||
return files;
|
||||
}
|
||||
|
||||
if (!is_inside_allowed_root(resolved_dir, resolved_root)) {
|
||||
error = _u8L("Resource texture directory is outside the plugin folder");
|
||||
return files;
|
||||
}
|
||||
|
||||
for (fs::directory_iterator it(resolved_dir, ec), end; it != end && !ec; it.increment(ec)) {
|
||||
const fs::path& path = it->path();
|
||||
|
||||
if (!fs::is_regular_file(path, ec) || ec)
|
||||
continue;
|
||||
|
||||
std::string ext = path.extension().string();
|
||||
boost::algorithm::to_lower(ext);
|
||||
if (ext != ".png" && ext != ".jpg" && ext != ".jpeg" && ext != ".bmp")
|
||||
continue;
|
||||
|
||||
// Re-check per file: a symlink inside the pack must not resolve outside it.
|
||||
if (!is_inside_allowed_root(path, resolved_root))
|
||||
continue;
|
||||
|
||||
files.push_back(path.string());
|
||||
if (files.size() >= kMaxResourceTextureFiles)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
error = _u8L("Resource texture directory could not be read");
|
||||
return {};
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -176,6 +176,12 @@ bool read_wheel_plugin_metadata(const boost::filesystem::path& whl_path, PluginD
|
||||
// Returns the path to the entry file, or an empty path with error set if zero or multiple candidates.
|
||||
boost::filesystem::path find_installed_plugin_entry(const boost::filesystem::path& plugin_dir, std::string& error);
|
||||
|
||||
// Validated non-recursive .png/.jpg/.jpeg/.bmp files directly inside `dir`, every one contained in
|
||||
// `plugin_root`. Empty with `error` set on failure.
|
||||
std::vector<std::string> collect_resource_texture_files(const std::string& dir,
|
||||
const std::string& plugin_root,
|
||||
std::string& error);
|
||||
|
||||
// Canonical writer: emits the .install_state.json schema for the given state.
|
||||
bool write_install_state(const boost::filesystem::path& plugin_dir, const PluginInstallState& state);
|
||||
// Builds a PluginInstallState from the descriptor and delegates to the canonical writer.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "PluginFsUtils.hpp"
|
||||
#include "PythonInterpreter.hpp"
|
||||
#include "PythonPluginBridge.hpp"
|
||||
#include "pluginTypes/resourceTexture/ResourceTexturePluginCapability.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
@@ -387,6 +388,37 @@ bool load(const PluginDescriptor& descriptor,
|
||||
instance->set_config_ui_available(false);
|
||||
}
|
||||
|
||||
// ResourceTexture capabilities resolve their texture directory here, under the
|
||||
// GIL this block already holds, and cache the validated result. A resolution
|
||||
// fault only costs this capability its textures, so it is caught locally like
|
||||
// has_config_ui() above rather than failing the load.
|
||||
if (auto* resource_texture = dynamic_cast<ResourceTexturePluginCapability*>(instance.get())) {
|
||||
try {
|
||||
const std::string texture_dir = resource_texture->get_texture_dir();
|
||||
if (texture_dir.empty()) {
|
||||
resource_texture->set_texture_dir({});
|
||||
} else {
|
||||
std::string texture_error;
|
||||
collect_resource_texture_files(texture_dir, descriptor.plugin_root, texture_error);
|
||||
if (texture_error.empty()) {
|
||||
resource_texture->set_texture_dir(texture_dir);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Plugin capability '" << found.name << "' of plugin '" << descriptor.plugin_key
|
||||
<< "': texture directory rejected (" << texture_error
|
||||
<< "); the capability will provide no textures";
|
||||
resource_texture->set_texture_dir({});
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Plugin capability '" << found.name << "' of plugin '" << descriptor.plugin_key
|
||||
<< "': get_texture_dir() failed (" << ex.what()
|
||||
<< "); the capability will provide no textures";
|
||||
resource_texture->set_texture_dir({});
|
||||
}
|
||||
}
|
||||
|
||||
capabilities.push_back(std::move(instance));
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "PyPluginTrampoline.hpp"
|
||||
#include "pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp"
|
||||
#include "pluginTypes/pages/PagesPluginCapability.hpp"
|
||||
#include "pluginTypes/resourceTexture/ResourceTexturePluginCapability.hpp"
|
||||
#include "pluginTypes/script/ScriptPluginCapability.hpp"
|
||||
#include "pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp"
|
||||
#include <pybind11/pytypes.h>
|
||||
@@ -374,6 +375,7 @@ void bind_python_api(pybind11::module_& m)
|
||||
.value("Visualization", PluginCapabilityType::Visualization)
|
||||
.value("Script", PluginCapabilityType::Script)
|
||||
.value("SlicingPipeline", PluginCapabilityType::SlicingPipeline)
|
||||
.value("ResourceTexture", PluginCapabilityType::ResourceTexture)
|
||||
.value("Unknown", PluginCapabilityType::Unknown)
|
||||
.export_values();
|
||||
|
||||
@@ -527,6 +529,7 @@ void bind_python_api(pybind11::module_& m)
|
||||
PagesPluginCapability::RegisterBindings(m);
|
||||
ScriptPluginCapability::RegisterBindings(m);
|
||||
SlicingPipelinePluginCapability::RegisterBindings(m);
|
||||
ResourceTexturePluginCapability::RegisterBindings(m);
|
||||
PluginHost::RegisterBindings(m);
|
||||
BOOST_LOG_TRIVIAL(debug) << "Registered ScriptPluginCapability Python bindings";
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class PluginCapabilityType { PrinterConnection = 0, Pages, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
|
||||
enum class PluginCapabilityType { PrinterConnection = 0, Pages, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, ResourceTexture, Unknown };
|
||||
|
||||
struct PluginCapabilityId
|
||||
{
|
||||
@@ -48,6 +48,7 @@ inline std::string plugin_capability_type_to_string(PluginCapabilityType type)
|
||||
case PluginCapabilityType::Visualization: return "visualization";
|
||||
case PluginCapabilityType::Script: return "script";
|
||||
case PluginCapabilityType::SlicingPipeline: return "slicing-pipeline";
|
||||
case PluginCapabilityType::ResourceTexture: return "resource-texture";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
@@ -63,6 +64,7 @@ inline std::string plugin_capability_type_display_name(PluginCapabilityType type
|
||||
case PluginCapabilityType::Visualization: return "Visualization";
|
||||
case PluginCapabilityType::Script: return "Script";
|
||||
case PluginCapabilityType::SlicingPipeline: return "Slicing Pipeline";
|
||||
case PluginCapabilityType::ResourceTexture: return "Resource Texture";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
@@ -92,6 +94,8 @@ inline PluginCapabilityType plugin_capability_type_from_string(std::string_view
|
||||
return PluginCapabilityType::Script;
|
||||
if (lowered == "slicing-pipeline")
|
||||
return PluginCapabilityType::SlicingPipeline;
|
||||
if (lowered == "resource-texture")
|
||||
return PluginCapabilityType::ResourceTexture;
|
||||
return PluginCapabilityType::Unknown;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "PluginResourceTextures.hpp"
|
||||
|
||||
#include "slic3r/plugin/PluginDescriptor.hpp"
|
||||
#include "slic3r/plugin/PluginManager.hpp"
|
||||
#include "slic3r/plugin/pluginTypes/resourceTexture/ResourceTexturePluginCapability.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
PluginResourceTextures &PluginResourceTextures::instance()
|
||||
{
|
||||
static PluginResourceTextures registry;
|
||||
return registry;
|
||||
}
|
||||
|
||||
std::optional<ResourceTextureEntry> PluginResourceTextures::build_entry(const PluginCapabilityId &id) const
|
||||
{
|
||||
// Query by id and re-check state; the manager copies the shared_ptr and releases its lock.
|
||||
auto capability = PluginManager::instance().get_plugin_capability(id, /*only_enabled=*/false);
|
||||
if (!capability || !capability->is_enabled() || capability->type() != PluginCapabilityType::ResourceTexture)
|
||||
return std::nullopt;
|
||||
|
||||
auto texture_capability = std::dynamic_pointer_cast<ResourceTexturePluginCapability>(capability);
|
||||
if (!texture_capability)
|
||||
return std::nullopt;
|
||||
|
||||
// An empty directory means resolution failed at load, so the pack contributes nothing.
|
||||
const std::string &texture_dir = texture_capability->texture_dir();
|
||||
if (texture_dir.empty())
|
||||
return std::nullopt;
|
||||
|
||||
PluginDescriptor descriptor;
|
||||
if (!PluginManager::instance().try_get_plugin_descriptor_for_capability(capability->name(),
|
||||
PluginCapabilityType::ResourceTexture,
|
||||
descriptor))
|
||||
return std::nullopt;
|
||||
|
||||
ResourceTextureEntry entry;
|
||||
entry.id = capability->identity();
|
||||
entry.plugin_key = entry.id.plugin_key;
|
||||
entry.display_name = capability->name();
|
||||
entry.texture_dir = texture_dir;
|
||||
entry.plugin_root = descriptor.plugin_root;
|
||||
return entry;
|
||||
}
|
||||
|
||||
void PluginResourceTextures::on_plugin_register(const std::string &plugin_key)
|
||||
{
|
||||
for (const auto &capability : PluginManager::instance().get_plugin_capabilities(plugin_key, PluginCapabilityType::ResourceTexture)) {
|
||||
if (capability)
|
||||
on_cap_register(capability->identity());
|
||||
}
|
||||
}
|
||||
|
||||
void PluginResourceTextures::on_plugin_deregister(const std::string &plugin_key)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for (auto it = m_entries.begin(); it != m_entries.end();) {
|
||||
if (it->first.plugin_key == plugin_key)
|
||||
it = m_entries.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void PluginResourceTextures::on_cap_register(const PluginCapabilityId &id)
|
||||
{
|
||||
std::optional<ResourceTextureEntry> entry = build_entry(id);
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (entry)
|
||||
m_entries[entry->id] = std::move(*entry);
|
||||
}
|
||||
|
||||
void PluginResourceTextures::on_cap_deregister(const PluginCapabilityId &id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_entries.erase(id);
|
||||
}
|
||||
|
||||
void PluginResourceTextures::initial_sweep()
|
||||
{
|
||||
std::map<PluginCapabilityId, ResourceTextureEntry> entries;
|
||||
for (const auto &capability : PluginManager::instance().get_plugin_capabilities("", PluginCapabilityType::ResourceTexture)) {
|
||||
if (!capability)
|
||||
continue;
|
||||
std::optional<ResourceTextureEntry> entry = build_entry(capability->identity());
|
||||
if (entry)
|
||||
entries[entry->id] = std::move(*entry);
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_entries = std::move(entries);
|
||||
}
|
||||
|
||||
std::vector<ResourceTextureEntry> PluginResourceTextures::entries() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
std::vector<ResourceTextureEntry> result;
|
||||
result.reserve(m_entries.size());
|
||||
for (const auto &entry : m_entries)
|
||||
result.push_back(entry.second);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// One ResourceTexture capability contributing a texture directory to the texture picker.
|
||||
struct ResourceTextureEntry
|
||||
{
|
||||
PluginCapabilityId id;
|
||||
std::string plugin_key;
|
||||
std::string display_name;
|
||||
std::string texture_dir;
|
||||
std::string plugin_root; // so a scan can re-check containment
|
||||
};
|
||||
|
||||
// Tracks every loaded and enabled ResourceTexture capability by id. Holds strings only: a
|
||||
// shared_ptr here would keep a Python object alive past Py_Finalize.
|
||||
class PluginResourceTextures
|
||||
{
|
||||
public:
|
||||
static PluginResourceTextures &instance();
|
||||
|
||||
PluginResourceTextures(const PluginResourceTextures &) = delete;
|
||||
PluginResourceTextures &operator=(const PluginResourceTextures &) = delete;
|
||||
|
||||
void on_plugin_register(const std::string &plugin_key);
|
||||
void on_plugin_deregister(const std::string &plugin_key);
|
||||
void on_cap_register(const PluginCapabilityId &id);
|
||||
void on_cap_deregister(const PluginCapabilityId &id);
|
||||
void initial_sweep();
|
||||
|
||||
std::vector<ResourceTextureEntry> entries() const;
|
||||
|
||||
private:
|
||||
PluginResourceTextures() = default;
|
||||
|
||||
std::optional<ResourceTextureEntry> build_entry(const PluginCapabilityId &id) const;
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
std::map<PluginCapabilityId, ResourceTextureEntry> m_entries;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,32 @@
|
||||
#include "ResourceTexturePluginCapability.hpp"
|
||||
#include "ResourceTexturePluginCapabilityTrampoline.hpp"
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/pytypes.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void ResourceTexturePluginCapability::RegisterBindings(pybind11::module_& module)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "Registering orca.resource_texture bindings";
|
||||
|
||||
auto resource_texture = module.def_submodule("resource_texture", "Plugin resource texture API");
|
||||
|
||||
py::class_<ResourceTexturePluginCapability, PluginCapabilityInterface,
|
||||
PyResourceTexturePluginCapabilityTrampoline,
|
||||
std::shared_ptr<ResourceTexturePluginCapability>>(resource_texture,
|
||||
"ResourceTexturePluginCapabilityBase")
|
||||
.def(py::init<>())
|
||||
.def("get_type", &ResourceTexturePluginCapability::get_type)
|
||||
.def("get_texture_dir", &ResourceTexturePluginCapability::get_texture_dir);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../PythonPluginInterface.hpp"
|
||||
#include "pybind11/pybind11.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Slic3r {
|
||||
class ResourceTexturePluginCapability : public PluginCapabilityInterface
|
||||
{
|
||||
public:
|
||||
static void RegisterBindings(pybind11::module_& module);
|
||||
|
||||
PluginCapabilityType get_type() const override { return PluginCapabilityType::ResourceTexture; }
|
||||
|
||||
// Python contract: the directory the host scans for textures. Resolved once at materialization
|
||||
// under the GIL; never called from the GUI. Overridden in Python through the trampoline.
|
||||
virtual std::string get_texture_dir() const = 0;
|
||||
|
||||
// C++-only read of the validated directory cached by the loader; empty if resolution failed.
|
||||
const std::string& texture_dir() const { return m_texture_dir; }
|
||||
void set_texture_dir(std::string dir) { m_texture_dir = std::move(dir); }
|
||||
|
||||
private:
|
||||
std::string m_texture_dir;
|
||||
};
|
||||
} // namespace Slic3r
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user