mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-12 18:31:18 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b736dd268 | ||
|
|
5dba611eac | ||
|
|
2feb5f0f9d | ||
|
|
9c164ca597 | ||
|
|
00bccf9b2d |
@@ -569,12 +569,16 @@ on the **first** call. So:
|
|||||||
#endif
|
#endif
|
||||||
```
|
```
|
||||||
|
|
||||||
**Before GTK starts**, only the environment exists. `CLI::run` (`src/OrcaSlicer.cpp`) branches on
|
**Before GTK starts**, only the environment exists. `CLI::run` (`src/OrcaSlicer.cpp`) sets the NVIDIA
|
||||||
`GDK_BACKEND` (an `x11` prefix means the X11 opt-in). On the default path it forces `GDK_BACKEND=x11`
|
DMABUF workaround and then branches on `GDK_BACKEND` (an `x11` prefix means the X11 opt-in). On both
|
||||||
when wx lacks EGL and `WAYLAND_DISPLAY` is set. It sets `WEBKIT_DISABLE_COMPOSITING_MODE=1`
|
paths, when `/proc/driver/nvidia/version` exists and the linked WebKitGTK honours the switch (>= 2.44,
|
||||||
(non-replacing) only when both `DISPLAY` and `WAYLAND_DISPLAY` are set, and calls `XInitThreads()`
|
`WebView::SupportsForceSharedMemory()`), it sets `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and
|
||||||
|
`WEBKIT_FORCE_DMABUF_RENDERER=1` (non-replacing): the proprietary driver cannot back WebKit's hardware
|
||||||
|
DMABUF renderer. On the default path it forces `GDK_BACKEND=x11` when wx lacks EGL and `WAYLAND_DISPLAY`
|
||||||
|
is set. It sets `WEBKIT_DISABLE_COMPOSITING_MODE=1` (non-replacing) only when both `DISPLAY` and
|
||||||
|
`WAYLAND_DISPLAY` are set and the linked WebKitGTK is older than 2.42, and calls `XInitThreads()`
|
||||||
only when `DISPLAY` is set. With neither variable set, the GUI refuses to start ("Neither DISPLAY nor
|
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.
|
`references/webview-gl-aui-media.md` §WebKitGTK on Linux sessions.
|
||||||
|
|
||||||
**wx's own detectors**, for reference:
|
**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
|
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)
|
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)
|
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 Orca's default-path XWayland case 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)
|
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
|
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
|
`WX_GL_DOUBLEBUFFER`; `wxGLAttributes`/`wxGLContextAttrs` end with `EndList()`; MSAA is requested
|
||||||
@@ -567,33 +568,66 @@ vue-resize workaround script and sets a persistent SQLite cookie store on the vi
|
|||||||
## WebKitGTK on Linux sessions
|
## WebKitGTK on Linux sessions
|
||||||
|
|
||||||
**Mechanism.** `CLI::run` (`src/OrcaSlicer.cpp`, under `#ifdef __WXGTK__`, before GTK initialises)
|
**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
|
- **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`
|
`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
|
non-replacing **only** when the linked WebKitGTK predates 2.42 (`WebView::NeedsCompositingWorkaround()`)
|
||||||
|
**and** both `DISPLAY` and `WAYLAND_DISPLAY` are non-empty (a Wayland session with
|
||||||
XWayland available — the session, not the backend GTK will pick); `XInitThreads()` runs only when
|
XWayland available — the session, not the backend GTK will pick); `XInitThreads()` runs only when
|
||||||
`DISPLAY` is set.
|
`DISPLAY` is set.
|
||||||
- **`GDK_BACKEND=x11…` opt-in path**: sets `DRI_PRIME=1` (and the NVIDIA PRIME variables when
|
- **`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
|
`/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, which keeps hardware acceleration for Device / Setup Wizard / login pages.
|
||||||
compositing on keeps hardware acceleration for Device / Setup Wizard / login pages).
|
|
||||||
|
|
||||||
Runtime backend detection elsewhere uses Orca's `is_running_on_wayland()` / `is_running_on_x11()`
|
Runtime backend detection elsewhere uses Orca's `is_running_on_wayland()` / `is_running_on_x11()`
|
||||||
(see `references/platforms.md`), not these environment variables.
|
(see `references/platforms.md`), not these environment variables.
|
||||||
|
|
||||||
**Pitfall.**
|
**Pitfall.**
|
||||||
|
- **Rule:** On NVIDIA reach for `WEBKIT_DMABUF_RENDERER_FORCE_SHM` before
|
||||||
|
`WEBKIT_DISABLE_COMPOSITING_MODE`. **Why:** the proprietary driver cannot back WebKit's hardware
|
||||||
|
DMABUF renderer, leaving web views blank or slow; FORCE_SHM keeps accelerated compositing and only
|
||||||
|
skips the hardware path, while disabling compositing drops it. Threaded scrolling is a separate,
|
||||||
|
release-dependent default (off in 2.44/2.45, on from 2.46). `WEBKIT_DISABLE_DMABUF_RENDERER` is not a
|
||||||
|
substitute — it is read first, leaves the transport set empty (no compositing from 2.44 on, where the
|
||||||
|
X11/Wayland fallback renderers were removed) and has a reported crash path. On Debian/Ubuntu and
|
||||||
|
openSUSE the `isNVIDIA()` patch returns before the FORCE_SHM check, so `WEBKIT_FORCE_DMABUF_RENDERER=1`
|
||||||
|
must clear it first; that variable exists only in those patches. WebKit reads all of these once per
|
||||||
|
process, before the first WebView.
|
||||||
|
The test is `/proc/driver/nvidia/version`, so a hybrid laptop that renders on its iGPU also takes the
|
||||||
|
shared-memory path, which is a per-frame GPU readback rather than zero-copy; exporting
|
||||||
|
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=0` in the environment restores the hardware path.
|
||||||
|
```cpp
|
||||||
|
// Wrong: kills compositing and threaded scrolling (and can crash on modern WebKit)
|
||||||
|
::setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1", false);
|
||||||
|
// Right: keep compositing, skip only the hardware DMABUF path
|
||||||
|
if (::access("/proc/driver/nvidia/version", F_OK) == 0 && WebView::SupportsForceSharedMemory()) {
|
||||||
|
::setenv("WEBKIT_DMABUF_RENDERER_FORCE_SHM", "1", /*replace*/ false);
|
||||||
|
::setenv("WEBKIT_FORCE_DMABUF_RENDERER", "1", /*replace*/ false);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Cite: WebKit 2.52.6 `Source/WebKit/UIProcess/gtk/AcceleratedBackingStore.cpp`
|
||||||
|
(`rendererBufferTransportMode()`), Debian `disable-nvidia-dmabuf.patch`, WebKit bug 262607.
|
||||||
- **Rule:** Set `WEBKIT_DISABLE_COMPOSITING_MODE` only on the default path in Wayland sessions,
|
- **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
|
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
|
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.
|
waits forever for a C++→JS call.
|
||||||
**Why:** WebKit2GTK accelerated compositing could fail silently under XWayland, leaving web views
|
**Why:** WebKit2GTK accelerated compositing could fail silently under XWayland before 2.42, leaving
|
||||||
blank (Setup Wizard stuck on "Loading..."); disabling it everywhere needlessly drops hardware
|
web views blank (Setup Wizard stuck on "Loading..."); the variable also turns off threaded scrolling,
|
||||||
acceleration; without the JS timeout the user is stuck when the signal is lost.
|
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
|
```cpp
|
||||||
// Wrong: unconditional, kills HW acceleration everywhere
|
// Wrong: unconditional, kills HW acceleration everywhere
|
||||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", false);
|
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", false);
|
||||||
// Right (default path only): non-replacing, before GTK init, Wayland session with XWayland
|
// Right (default path only): non-replacing, before GTK init, WebKitGTK < 2.42,
|
||||||
if (display && *display && wayland && *wayland)
|
// Wayland session with XWayland
|
||||||
|
if (WebView::NeedsCompositingWorkaround() && display && *display && wayland && *wayland)
|
||||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /*replace*/ false);
|
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /*replace*/ false);
|
||||||
```
|
```
|
||||||
Cite: c12912e0df (`src/OrcaSlicer.cpp`; `resources/web/guide/0/load.js` `OnInit`
|
Cite: c12912e0df (`src/OrcaSlicer.cpp`; `resources/web/guide/0/load.js` `OnInit`
|
||||||
|
|||||||
@@ -16,15 +16,6 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
# Listing caches and builds while waiting for a deps cache.
|
|
||||||
actions: read
|
|
||||||
|
|
||||||
# Read by scripts/ci_deps_cache.sh.
|
|
||||||
env:
|
|
||||||
REPO: ${{ github.repository }}
|
|
||||||
WORKFLOW: build_all.yml
|
|
||||||
BASE_REF: ${{ github.base_ref }}
|
|
||||||
JOB: build_linux (${{ vars.SELF_HOSTED && 'orca-lnx-server' || 'ubuntu-24.04' }})
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||||
@@ -44,7 +35,7 @@ jobs:
|
|||||||
# The PR merge commit plus its first parent, the base it is diffed against.
|
# The PR merge commit plus its first parent, the base it is diffed against.
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
|
|
||||||
- name: Look for changed files
|
- name: Look for changed C++ files
|
||||||
id: changes
|
id: changes
|
||||||
run: |
|
run: |
|
||||||
if git -c core.quotePath=false diff --name-only HEAD^1 -- src tests | grep -qE '\.(cpp|cc|cxx|hpp|h|hxx)$'; then
|
if git -c core.quotePath=false diff --name-only HEAD^1 -- src tests | grep -qE '\.(cpp|cc|cxx|hpp|h|hxx)$'; then
|
||||||
@@ -52,75 +43,18 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "No C++ changes under src/ or tests/."
|
echo "No C++ changes under src/ or tests/."
|
||||||
fi
|
fi
|
||||||
if ! git diff --quiet HEAD^1 -- deps; then
|
|
||||||
echo "deps=true" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
echo "deps-key=linux-clang-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Parsing needs the dependency headers, not a build of this PR's deps/.
|
# Parsing needs the dependency headers, not a build of this PR's deps/, so
|
||||||
# Without an exact match the job waits for the build that saves it: this
|
# a PR that changes deps/ falls back to the newest cache main has.
|
||||||
# PR's own when it changes deps/, otherwise a running push build of the
|
|
||||||
# base branch with the same deps. With neither, a PR that keeps deps/
|
|
||||||
# falls back to the newest cache.
|
|
||||||
- name: Restore cached deps
|
- name: Restore cached deps
|
||||||
id: deps_cache
|
|
||||||
if: steps.changes.outputs.cpp == 'true'
|
if: steps.changes.outputs.cpp == 'true'
|
||||||
uses: actions/cache/restore@v6
|
uses: actions/cache/restore@v6
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
|
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
|
||||||
key: ${{ steps.changes.outputs.deps-key }}
|
key: linux-clang-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }}
|
||||||
|
|
||||||
- name: Look for a build to wait for
|
|
||||||
id: wait_check
|
|
||||||
if: steps.changes.outputs.cpp == 'true' && steps.deps_cache.outputs.cache-hit != 'true'
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
||||||
run: bash scripts/ci_deps_cache.sh ${{ steps.changes.outputs.deps == 'true' && 'own' || 'check' }}
|
|
||||||
|
|
||||||
# Linux Build Deps takes about 35 minutes on GitHub-hosted runners. The
|
|
||||||
# wait ends early when the deps job fails.
|
|
||||||
- name: Wait for the deps cache
|
|
||||||
if: steps.wait_check.outputs.wait-for != ''
|
|
||||||
timeout-minutes: 100
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
KEY: ${{ steps.changes.outputs.deps-key }}
|
|
||||||
CACHE_REF: ${{ steps.changes.outputs.deps == 'true' && github.ref || format('refs/heads/{0}', github.base_ref) }}
|
|
||||||
KEY_INPUTS: ${{ steps.wait_check.outputs.key-inputs }}
|
|
||||||
WAIT_FOR: ${{ steps.wait_check.outputs.wait-for }}
|
|
||||||
WAIT_SINCE: ${{ steps.wait_check.outputs.wait-since }}
|
|
||||||
WAIT_MINUTES: 90
|
|
||||||
run: bash scripts/ci_deps_cache.sh wait
|
|
||||||
|
|
||||||
- name: Restore the deps cache after waiting
|
|
||||||
id: waited_cache
|
|
||||||
if: steps.wait_check.outputs.wait-for != ''
|
|
||||||
uses: actions/cache/restore@v6
|
|
||||||
with:
|
|
||||||
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
|
|
||||||
key: ${{ steps.changes.outputs.deps-key }}
|
|
||||||
|
|
||||||
- name: Fail without this PR's deps cache
|
|
||||||
if: steps.changes.outputs.deps == 'true' && steps.wait_check.outcome == 'success' && steps.waited_cache.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
echo "::error::No deps cache ${{ steps.changes.outputs.deps-key }}. Check this PR's Linux Build Deps job, then rerun clang-tidy."
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
- name: Restore the newest cached deps
|
|
||||||
id: newest_cache
|
|
||||||
if: steps.changes.outputs.deps != 'true' && steps.wait_check.outcome == 'success' && steps.waited_cache.outputs.cache-hit != 'true'
|
|
||||||
uses: actions/cache/restore@v6
|
|
||||||
with:
|
|
||||||
path: ${{ github.workspace }}/deps/build/OrcaSlicer_dep
|
|
||||||
key: ${{ steps.changes.outputs.deps-key }}
|
|
||||||
restore-keys: linux-clang-cache-orcaslicer_deps-build-
|
restore-keys: linux-clang-cache-orcaslicer_deps-build-
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
|
|
||||||
- name: Warn about older deps
|
|
||||||
if: steps.newest_cache.outcome == 'success' && steps.newest_cache.outputs.cache-hit != 'true'
|
|
||||||
run: echo "::warning title=Older deps::No cache matches this PR's deps/, so clang-tidy parsed against ${{ steps.newest_cache.outputs.cache-matched-key }}. If it reports errors in code that uses a dependency, rerun clang-tidy once the base branch's Build all has saved the new deps."
|
|
||||||
|
|
||||||
- name: Apt-Install Dependencies
|
- name: Apt-Install Dependencies
|
||||||
if: steps.changes.outputs.cpp == 'true' && !vars.SELF_HOSTED
|
if: steps.changes.outputs.cpp == 'true' && !vars.SELF_HOSTED
|
||||||
uses: ./.github/actions/apt-install-deps
|
uses: ./.github/actions/apt-install-deps
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
# ccache does not parse the -clang: arguments CMake uses for clang-cl's gcc-style
|
# ccache does not parse the -clang: arguments CMake uses for clang-cl's gcc-style
|
||||||
# depfile, so a cache hit writes the object and no depfile, and Ninja then records
|
# depfile, so a cache hit writes the object and no depfile, and Ninja then records
|
||||||
# no headers for that object. ccache reproduces /showIncludes output on a hit.
|
# no headers for that object. ccache reproduces /showIncludes output on a hit.
|
||||||
#
|
|
||||||
# Clang also records the modification time of every input in a precompiled header
|
|
||||||
# and rejects the header once one differs. ccache caches the compile that creates
|
|
||||||
# the header, so a tree that regenerates cmake_pch.cxx gets back a header stamped
|
|
||||||
# with the old file's time, and every compile that uses it fails. The build system
|
|
||||||
# already rebuilds the header when an input changes. This covers the dependencies,
|
|
||||||
# such as wxWidgets; PrecompiledHeader.cmake does the same for OrcaSlicer's targets.
|
|
||||||
foreach (_lang C CXX)
|
foreach (_lang C CXX)
|
||||||
if (CMAKE_${_lang}_COMPILER_ID STREQUAL "Clang" AND
|
if (CMAKE_${_lang}_COMPILER_ID STREQUAL "Clang" AND
|
||||||
CMAKE_${_lang}_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
CMAKE_${_lang}_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||||
set(CMAKE_DEPFILE_FLAGS_${_lang} "/showIncludes")
|
set(CMAKE_DEPFILE_FLAGS_${_lang} "/showIncludes")
|
||||||
set(CMAKE_${_lang}_DEPFILE_FORMAT msvc)
|
set(CMAKE_${_lang}_DEPFILE_FORMAT msvc)
|
||||||
list(APPEND CMAKE_${_lang}_COMPILE_OPTIONS_CREATE_PCH -Xclang -fno-pch-timestamp)
|
|
||||||
endif ()
|
endif ()
|
||||||
endforeach ()
|
endforeach ()
|
||||||
|
|||||||
@@ -7478,7 +7478,7 @@ msgid "Update"
|
|||||||
msgstr "更新"
|
msgstr "更新"
|
||||||
|
|
||||||
msgid "Assistant(HMS)"
|
msgid "Assistant(HMS)"
|
||||||
msgstr "助手(HMS)"
|
msgstr "助理(HMS)"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "Network plug-in v%s"
|
msgid "Network plug-in v%s"
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<svg width="28" height="27" viewBox="0 0 28 27" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path transform="matrix(0.9 0 0 0.9 1.4 1.35)" d="M11.9888 2.01601C12.8984 0.494662 15.1024 0.494662 16.012 2.01601L18.8677 6.79237C19.1958 7.34113 19.7339 7.73201 20.3572 7.87447L25.7821 9.11446C27.5101 9.50943 28.1913 11.6056 27.0254 12.9408L23.3652 17.1327C22.9449 17.6143 22.7394 18.2469 22.7964 18.8836L23.2936 24.4263C23.4519 26.1918 21.6687 27.4873 20.0386 26.7911L14.9209 24.6056C14.3329 24.3545 13.6679 24.3545 13.0799 24.6056L7.96209 26.7911C6.33196 27.4873 4.54887 26.1918 4.70722 24.4263L5.20434 18.8836C5.26145 18.2469 5.05595 17.6143 4.63544 17.1327L0.97533 12.9408C-0.19048 11.6056 0.490599 9.50943 2.21859 9.11446L7.64364 7.87447C8.2669 7.73201 8.80494 7.34113 9.13302 6.79237L11.9888 2.01601Z" stroke="#949494" stroke-width="2.4" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 876 B |
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Bambulab",
|
"name": "Bambulab",
|
||||||
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
|
"url": "http://www.bambulab.com/Parameters/vendor/BBL.json",
|
||||||
"version": "02.08.00.15",
|
"version": "02.08.00.14",
|
||||||
"force_update": "0",
|
"force_update": "0",
|
||||||
"description": "the initial version of BBL configurations",
|
"description": "the initial version of BBL configurations",
|
||||||
"machine_model_list": [
|
"machine_model_list": [
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Qidi",
|
"name": "Qidi",
|
||||||
"version": "02.04.00.17",
|
"version": "02.04.00.16",
|
||||||
"force_update": "0",
|
"force_update": "0",
|
||||||
"description": "Qidi configurations",
|
"description": "Qidi configurations",
|
||||||
"machine_model_list": [
|
"machine_model_list": [
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
"extruder_clearance_height_to_lid": "120",
|
"extruder_clearance_height_to_lid": "120",
|
||||||
"layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nLOG_Z\nG92 E0\n",
|
"layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nLOG_Z\nG92 E0\n",
|
||||||
"machine_end_gcode": "M141 S0\nM104 S0\nM140 S0\nG1 E-3 F1800\nG0 Z{min(max_print_height, max_layer_z + 3)} F600\nG0 X0 Y0 F12000\n{if max_layer_z < max_print_height / 2}G1 Z{max_print_height / 2 + 10} F600{else}G1 Z{min(max_print_height, max_layer_z + 3)}{endif}",
|
"machine_end_gcode": "M141 S0\nM104 S0\nM140 S0\nG1 E-3 F1800\nG0 Z{min(max_print_height, max_layer_z + 3)} F600\nG0 X0 Y0 F12000\n{if max_layer_z < max_print_height / 2}G1 Z{max_print_height / 2 + 10} F600{else}G1 Z{min(max_print_height, max_layer_z + 3)}{endif}",
|
||||||
"machine_start_gcode": "PRINT_START BED=[bed_temperature_initial_layer_single] HOTEND=[nozzle_temperature_initial_layer] CHAMBER=[chamber_temperature]\nM83\nM140 S[bed_temperature_initial_layer_single]\nM104 S[nozzle_temperature_initial_layer]\nG4 P3000\nG0 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0)} Z5 F6000\nG0 Z[initial_layer_print_height] F600\nG1 E3 F1800\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 80))} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0)} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 85} E{83 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 3} E{82 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 3} Z0\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 6}\nG1 Z1 F600\n",
|
"machine_start_gcode": "PRINT_START BED=[hot_plate_temp_initial_layer] HOTEND=[nozzle_temperature_initial_layer] CHAMBER=[chamber_temperature]\nM83\nM140 S[hot_plate_temp_initial_layer]\nM104 S[nozzle_temperature_initial_layer]\nG4 P3000\nG0 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0)} Z5 F6000\nG0 Z[initial_layer_print_height] F600\nG1 E3 F1800\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 80))} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0)} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 85} E{83 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1] - 5, first_layer_print_min[1] + 80) - 85), 0) + 3} E{82 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 3} Z0\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85), 0) + 6}\nG1 Z1 F600\n",
|
||||||
"thumbnails_format": "PNG",
|
"thumbnails_format": "PNG",
|
||||||
"default_filament_profile": [
|
"default_filament_profile": [
|
||||||
"Generic PLA @Qidi Q1 Pro 0.4 nozzle"
|
"Generic PLA @Qidi Q1 Pro 0.4 nozzle"
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
"default_filament_profile": [
|
"default_filament_profile": [
|
||||||
"Generic PLA @Qidi"
|
"Generic PLA @Qidi"
|
||||||
],
|
],
|
||||||
"machine_start_gcode": "G28\nM140 S[bed_temperature_initial_layer_single]\nM190 S[bed_temperature_initial_layer_single]\nM109 S[nozzle_temperature_initial_layer]\nG92 E-19\nG0 Y5 Z0.3 F3600\nG1 X5 E0 F2400\n",
|
"machine_start_gcode": "G28\nM140 S[hot_plate_temp_initial_layer]\nM190 S[hot_plate_temp_initial_layer]\nM109 S[nozzle_temperature_initial_layer]\nG92 E-19\nG0 Y5 Z0.3 F3600\nG1 X5 E0 F2400\n",
|
||||||
"machine_end_gcode": "M104 S0\nM140 S0\n;Retract the filament\nG92 E0\nG1 E-3 F300\nG28\nM84\n",
|
"machine_end_gcode": "M104 S0\nM140 S0\n;Retract the filament\nG92 E0\nG1 E-3 F300\nG28\nM84\n",
|
||||||
"scan_first_layer": "0"
|
"scan_first_layer": "0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"210x210/COLPIC",
|
"210x210/COLPIC",
|
||||||
"110x110/PNG"
|
"110x110/PNG"
|
||||||
],
|
],
|
||||||
"machine_start_gcode": "PRINT_START\nG28\nM141 S0\nG0 Z50 F600\nM190 S[bed_temperature_initial_layer_single]\nG28 Z\nG29; mesh bed leveling ,comment this code to close it\nG0 X0 Y0 Z50 F6000\nM141 S{overall_chamber_temperature}\nM109 S[nozzle_temperature_initial_layer]\nM106 P3 S255\nM83\nG4 P3000\nG0 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0)} Z5 F6000\nG0 Z[initial_layer_print_height] F600\nG1 E3 F1800\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 80))} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 85} E{83 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 3} E{82 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0) + 12} E{-10 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 E{10 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\n",
|
"machine_start_gcode": "PRINT_START\nG28\nM141 S0\nG0 Z50 F600\nM190 S[hot_plate_temp_initial_layer]\nG28 Z\nG29; mesh bed leveling ,comment this code to close it\nG0 X0 Y0 Z50 F6000\nM141 S{overall_chamber_temperature}\nM109 S[nozzle_temperature_initial_layer]\nM106 P3 S255\nM83\nG4 P3000\nG0 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0)} Z5 F6000\nG0 Z[initial_layer_print_height] F600\nG1 E3 F1800\nG1 X{(min(print_bed_max[0], first_layer_print_min[0] + 80))} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0)} E{85 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 85} E{83 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0) + 2} E{2 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 Y{max((min(print_bed_max[1], first_layer_print_min[1] + 80) - 85),0) + 3} E{82 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 X{max((min(print_bed_max[0], first_layer_print_min[0] + 80) - 85),0) + 12} E{-10 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\nG1 E{10 * 0.5 * initial_layer_print_height * nozzle_diameter[0]} F3000\n",
|
||||||
"machine_end_gcode": "M141 S0\nM104 S0\nM140 S0\nG1 E-3 F1800\nG0 Z{min(max_print_height, max_layer_z + 3)} F600\nG0 X0 Y0 F12000\n{if max_layer_z < max_print_height / 2}G1 Z{max_print_height / 2 + 10} F600{else}G1 Z{min(max_print_height, max_layer_z + 3)}{endif}",
|
"machine_end_gcode": "M141 S0\nM104 S0\nM140 S0\nG1 E-3 F1800\nG0 Z{min(max_print_height, max_layer_z + 3)} F600\nG0 X0 Y0 F12000\n{if max_layer_z < max_print_height / 2}G1 Z{max_print_height / 2 + 10} F600{else}G1 Z{min(max_print_height, max_layer_z + 3)}{endif}",
|
||||||
"scan_first_layer": "0"
|
"scan_first_layer": "0"
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-43
@@ -1,27 +1,21 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Lets a pull request's macOS deps build, or its clang-tidy job, wait for a
|
# Lets a pull request's macOS deps build wait for a push build of the base
|
||||||
# build that saves the deps cache it needs, from a Linux job. `check` runs after
|
# branch that builds the same deps, from a Linux job. `check` runs after a cache
|
||||||
# a cache miss and names a queued or running push build of the base branch with
|
# miss and names a queued or running base build with the same deps/ and .github/
|
||||||
# the same deps/ and .github/ to wait for, unless the pull request changes either.
|
# to wait for, unless the pull request changes either. `wait` waits until that
|
||||||
# `own` names the pull request's own build instead, for clang-tidy when the pull
|
# build saves KEY. actions/cache then confirms the entry can be restored.
|
||||||
# request changes deps/. `wait` waits until that build saves KEY under CACHE_REF,
|
|
||||||
# or its deps job fails. actions/cache then confirms the entry can be restored.
|
|
||||||
#
|
#
|
||||||
# Environment: GH_TOKEN, REPO, WORKFLOW_REF (or WORKFLOW, the build workflow's
|
# Environment: GH_TOKEN, REPO, WORKFLOW_REF and BASE_REF, plus KEY_INPUTS,
|
||||||
# file name) and BASE_REF, plus HEAD_SHA for `own`, and KEY_INPUTS, WAIT_FOR,
|
# WAIT_FOR, WAIT_SINCE, WAIT_MINUTES, KEY and ARCH for `wait`.
|
||||||
# WAIT_SINCE, WAIT_MINUTES, KEY, CACHE_REF (default the base branch) and ARCH
|
|
||||||
# (or JOB, the matrix job's name) for `wait`.
|
|
||||||
# Writes key-inputs, wait-for and wait-since to $GITHUB_OUTPUT.
|
# Writes key-inputs, wait-for and wait-since to $GITHUB_OUTPUT.
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
|
|
||||||
workflow=${WORKFLOW_REF:-}
|
workflow=${WORKFLOW_REF%%@*}
|
||||||
workflow=${workflow%%@*}
|
workflow=${workflow##*/}
|
||||||
workflow=${WORKFLOW:-${workflow##*/}}
|
|
||||||
|
|
||||||
# Must match the job names in build_all.yml, build_check_cache.yml and build_deps.yml.
|
# Must match the job names in build_all.yml, build_check_cache.yml and build_deps.yml.
|
||||||
job=${JOB:-build_macos_arch (${ARCH:-})}
|
check_job_name="build_macos_arch (${ARCH:-}) / Check Cache"
|
||||||
check_job_name="$job / Check Cache"
|
deps_job_name="build_macos_arch (${ARCH:-}) / Build Deps / Build Deps"
|
||||||
deps_job_name="$job / Build Deps / Build Deps"
|
|
||||||
|
|
||||||
out() { echo "$1" >> "${GITHUB_OUTPUT:-/dev/stdout}"; }
|
out() { echo "$1" >> "${GITHUB_OUTPUT:-/dev/stdout}"; }
|
||||||
|
|
||||||
@@ -69,27 +63,8 @@ check() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
own() {
|
|
||||||
out "key-inputs=$(local_key_inputs HEAD)"
|
|
||||||
local id
|
|
||||||
# The build can be created a moment after this job.
|
|
||||||
for _ in 1 2 3 4 5; do
|
|
||||||
id=$(gh api -X GET "repos/$REPO/actions/workflows/$workflow/runs" \
|
|
||||||
-f head_sha="$HEAD_SHA" -f event=pull_request -f per_page=1 --jq '.workflow_runs[0].id // empty' < /dev/null)
|
|
||||||
[ -n "$id" ] && break
|
|
||||||
sleep 30
|
|
||||||
done
|
|
||||||
if [ -n "$id" ]; then
|
|
||||||
echo "Waiting for the deps cache of build $id of this pull request."
|
|
||||||
out "wait-for=$id"
|
|
||||||
out "wait-since=$(date +%s)"
|
|
||||||
else
|
|
||||||
echo "No build of this pull request is building its deps."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
key_saved() {
|
key_saved() {
|
||||||
gh cache list -R "$REPO" --ref "${CACHE_REF:-refs/heads/$BASE_REF}" --key "$KEY" --json key < /dev/null \
|
gh cache list -R "$REPO" --ref "refs/heads/$BASE_REF" --key "$KEY" --json key < /dev/null \
|
||||||
| jq -e --arg key "$KEY" 'any(.[]; .key == $key)' > /dev/null
|
| jq -e --arg key "$KEY" 'any(.[]; .key == $key)' > /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +87,11 @@ wait_for() {
|
|||||||
key_saved && { echo "Found $KEY."; return; }
|
key_saved && { echo "Found $KEY."; return; }
|
||||||
sleep 20
|
sleep 20
|
||||||
done
|
done
|
||||||
echo "Build $id finished its deps without saving $KEY."
|
echo "Build $id of $BASE_REF finished its deps without saving $KEY."
|
||||||
return ;;
|
return ;;
|
||||||
"completed cancelled") ;;
|
"completed cancelled") ;;
|
||||||
completed*)
|
completed*)
|
||||||
echo "The deps job of build $id did not succeed."
|
echo "The deps job of build $id of $BASE_REF did not succeed."
|
||||||
return ;;
|
return ;;
|
||||||
# The deps job exists only once the build's cache check has succeeded.
|
# The deps job exists only once the build's cache check has succeeded.
|
||||||
"")
|
"")
|
||||||
@@ -125,7 +100,7 @@ wait_for() {
|
|||||||
completed*)
|
completed*)
|
||||||
check=$(jq -r --arg name "$check_job_name" 'first(.jobs[] | select(.name == $name) | .conclusion) // ""' <<< "$jobs")
|
check=$(jq -r --arg name "$check_job_name" 'first(.jobs[] | select(.name == $name) | .conclusion) // ""' <<< "$jobs")
|
||||||
if [ -z "$warned" ] && { [ -z "$check" ] || [ "$check" = success ]; }; then
|
if [ -z "$warned" ] && { [ -z "$check" ] || [ "$check" = success ]; }; then
|
||||||
echo "::warning title=Deps wait::Build $id has no job named \"$deps_job_name\". If the build jobs were renamed, update scripts/ci_deps_cache.sh. This does not affect this pull request."
|
echo "::warning title=Deps wait::Build $id of $BASE_REF has no job named \"$deps_job_name\". If the build jobs were renamed, update scripts/ci_deps_cache.sh. This does not affect this pull request."
|
||||||
warned=1
|
warned=1
|
||||||
fi ;;
|
fi ;;
|
||||||
*)
|
*)
|
||||||
@@ -143,12 +118,11 @@ wait_for() {
|
|||||||
seen="$seen $id"
|
seen="$seen $id"
|
||||||
echo "Following build $id of $BASE_REF."
|
echo "Following build $id of $BASE_REF."
|
||||||
done
|
done
|
||||||
echo "No build saved $KEY."
|
echo "No build of $BASE_REF saved $KEY."
|
||||||
}
|
}
|
||||||
|
|
||||||
case "${1:-}" in
|
case "${1:-}" in
|
||||||
check) check ;;
|
check) check ;;
|
||||||
own) own ;;
|
|
||||||
wait) wait_for ;;
|
wait) wait_for ;;
|
||||||
*) echo "usage: $0 check|own|wait" >&2; exit 2 ;;
|
*) echo "usage: $0 check|wait" >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
# Work-around https://gitlab.gnome.org/GNOME/gnome-build-meta/-/issues/754
|
# WebKit's NVIDIA DMABUF workaround (gnome-build-meta#754) is applied by the
|
||||||
grep -q org.freedesktop.Platform.GL.nvidia /.flatpak-info && export WEBKIT_DISABLE_DMABUF_RENDERER=1
|
# binary itself, gated on the runtime WebKitGTK version (src/OrcaSlicer.cpp).
|
||||||
|
|
||||||
# Work-around https://github.com/bambulab/BambuStudio/issues/3440
|
# Work-around https://github.com/bambulab/BambuStudio/issues/3440
|
||||||
# Use LC_NUMERIC instead of LC_ALL to prevent decimal separator issues
|
# Use LC_NUMERIC instead of LC_ALL to prevent decimal separator issues
|
||||||
|
|||||||
+18
-8
@@ -161,6 +161,7 @@ using json = nlohmann::json;
|
|||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
#endif
|
#endif
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include "slic3r/GUI/Widgets/WebView.hpp"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef SLIC3R_GUI
|
#ifdef SLIC3R_GUI
|
||||||
@@ -1153,8 +1154,9 @@ int CLI::run(int argc, char **argv)
|
|||||||
// opt-in path. The ~2020-era WebKit2GTK XWayland compositor bug it
|
// opt-in path. The ~2020-era WebKit2GTK XWayland compositor bug it
|
||||||
// worked around appears fixed in WebKit2GTK >= 2.42; leaving it
|
// worked around appears fixed in WebKit2GTK >= 2.42; leaving it
|
||||||
// unset preserves WebKit hardware acceleration on Device / Setup
|
// unset preserves WebKit hardware acceleration on Device / Setup
|
||||||
// Wizard / login / store. The default path still applies it on
|
// Wizard / login / store. The default path applies it only on
|
||||||
// XWayland sessions as a conservative fallback for older WebKit.
|
// XWayland sessions 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");
|
const char* gdk_backend = ::getenv("GDK_BACKEND");
|
||||||
@@ -1162,6 +1164,14 @@ int CLI::run(int argc, char **argv)
|
|||||||
// honours the first backend in the comma-separated list).
|
// honours the first backend in the comma-separated list).
|
||||||
const bool x11_opt_in = gdk_backend && boost::starts_with(gdk_backend, "x11");
|
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) {
|
if (x11_opt_in) {
|
||||||
// ===== X11 / XWayland (opted in via GDK_BACKEND=x11) =====
|
// ===== X11 / XWayland (opted in via GDK_BACKEND=x11) =====
|
||||||
BOOST_LOG_TRIVIAL(info) << "GDK_BACKEND=x11 detected; applying v2.3.2 X11/XWayland workarounds.";
|
BOOST_LOG_TRIVIAL(info) << "GDK_BACKEND=x11 detected; applying v2.3.2 X11/XWayland workarounds.";
|
||||||
@@ -1205,15 +1215,15 @@ int CLI::run(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// WebKit2GTK compositing can fail under XWayland on older
|
// WebKit2GTK accelerated compositing could fail under XWayland
|
||||||
// WebKit releases. Disable it only when both DISPLAY and
|
// on WebKit releases before 2.42. Disable it only then, and only
|
||||||
// WAYLAND_DISPLAY are set (i.e. an XWayland session is in
|
// when both DISPLAY and WAYLAND_DISPLAY are set (an XWayland
|
||||||
// use). On pure X11 or native Wayland, compositing is left
|
// session). Disabling it also turns off threaded scrolling, so on
|
||||||
// enabled so WebKit retains HW acceleration.
|
// newer WebKit it only costs performance.
|
||||||
{
|
{
|
||||||
const char* display_env_wk = ::getenv("DISPLAY");
|
const char* display_env_wk = ::getenv("DISPLAY");
|
||||||
const char* wayland_env_wk = ::getenv("WAYLAND_DISPLAY");
|
const char* wayland_env_wk = ::getenv("WAYLAND_DISPLAY");
|
||||||
if (display_env_wk && *display_env_wk && wayland_env_wk && *wayland_env_wk) {
|
if (WebView::NeedsCompositingWorkaround() && display_env_wk && *display_env_wk && wayland_env_wk && *wayland_env_wk) {
|
||||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false);
|
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json
|
||||||
export MESA_LOADER_DRIVER_OVERRIDE=zink
|
export MESA_LOADER_DRIVER_OVERRIDE=zink
|
||||||
export GALLIUM_DRIVER=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
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
#include "libslic3r/BrimEarsPoint.hpp"
|
#include "libslic3r/BrimEarsPoint.hpp"
|
||||||
#include "libslic3r/Config.hpp"
|
#include "libslic3r/Config.hpp"
|
||||||
#include "libslic3r/CustomGCode.hpp"
|
#include "libslic3r/CustomGCode.hpp"
|
||||||
#include "libslic3r/TextureDisplacement.hpp"
|
|
||||||
#include "libslic3r_version.h"
|
#include "libslic3r_version.h"
|
||||||
|
|
||||||
#include "bbs_3mf.hpp"
|
#include "bbs_3mf.hpp"
|
||||||
@@ -355,13 +354,6 @@ static constexpr const char* CUSTOM_SUPPORTS_ATTR = "paint_supports";
|
|||||||
static constexpr const char* CUSTOM_FUZZY_SKIN_ATTR = "paint_fuzzy_skin";
|
static constexpr const char* CUSTOM_FUZZY_SKIN_ATTR = "paint_fuzzy_skin";
|
||||||
static constexpr const char* CUSTOM_SEAM_ATTR = "paint_seam";
|
static constexpr const char* CUSTOM_SEAM_ATTR = "paint_seam";
|
||||||
static constexpr const char* MMU_SEGMENTATION_ATTR = "paint_color";
|
static constexpr const char* MMU_SEGMENTATION_ATTR = "paint_color";
|
||||||
// Texture displacement. One paint mask per layer slot, mirroring paint_color; the layer stack itself is
|
|
||||||
// a JSON file in the archive, named by the volume metadata key below (see add_texture_displacement()).
|
|
||||||
static constexpr const char* TEXTURE_DISPLACEMENT_ATTRS[Slic3r::TEXTURE_DISPLACEMENT_MAX_LAYERS] = {
|
|
||||||
"paint_texture_0", "paint_texture_1", "paint_texture_2", "paint_texture_3",
|
|
||||||
"paint_texture_4", "paint_texture_5", "paint_texture_6", "paint_texture_7" };
|
|
||||||
static constexpr const char* TEXTURE_DISPLACEMENT_KEY = "texture_displacement";
|
|
||||||
static constexpr const char* TEXTURE_DISPLACEMENT_DIR = "Metadata/texture_displacement/";
|
|
||||||
// BBS
|
// BBS
|
||||||
static constexpr const char* FACE_PROPERTY_ATTR = "face_property";
|
static constexpr const char* FACE_PROPERTY_ATTR = "face_property";
|
||||||
|
|
||||||
@@ -855,9 +847,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
std::vector<std::string> custom_seam;
|
std::vector<std::string> custom_seam;
|
||||||
std::vector<std::string> mmu_segmentation;
|
std::vector<std::string> mmu_segmentation;
|
||||||
std::vector<std::string> fuzzy_skin;
|
std::vector<std::string> fuzzy_skin;
|
||||||
// One per texture displacement layer slot, indexed like `triangles` but stored only as far as the
|
|
||||||
// slot's last painted triangle: empty when the slot has no paint, which is nearly every file.
|
|
||||||
std::vector<std::string> texture_displacement[TEXTURE_DISPLACEMENT_MAX_LAYERS];
|
|
||||||
// BBS
|
// BBS
|
||||||
std::vector<std::string> face_properties;
|
std::vector<std::string> face_properties;
|
||||||
|
|
||||||
@@ -878,8 +867,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
custom_seam.clear();
|
custom_seam.clear();
|
||||||
mmu_segmentation.clear();
|
mmu_segmentation.clear();
|
||||||
fuzzy_skin.clear();
|
fuzzy_skin.clear();
|
||||||
for (std::vector<std::string> &slot : texture_displacement)
|
|
||||||
slot.clear();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1213,11 +1200,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
/*IdToSlaSupportPointsMap m_sla_support_points;
|
/*IdToSlaSupportPointsMap m_sla_support_points;
|
||||||
IdToSlaDrainHolesMap m_sla_drain_holes;*/
|
IdToSlaDrainHolesMap m_sla_drain_holes;*/
|
||||||
PathToEmbossShapeFileMap m_path_to_emboss_shape_files;
|
PathToEmbossShapeFileMap m_path_to_emboss_shape_files;
|
||||||
// Texture displacement: every file under Metadata/texture_displacement/, by archive path. Volumes
|
|
||||||
// are built only after the whole archive has been walked, so by the time a volume names its JSON
|
|
||||||
// every file it could refer to is already in here, whatever order the archive happened to be in.
|
|
||||||
std::map<std::string, std::string> m_texture_displacement_files;
|
|
||||||
void _apply_texture_displacement(ModelVolume &volume, const std::string &json_path);
|
|
||||||
std::string m_curr_metadata_name;
|
std::string m_curr_metadata_name;
|
||||||
std::string m_curr_characters;
|
std::string m_curr_characters;
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
@@ -1747,8 +1729,8 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
//plate->pattern_file = it->second->pattern_file;
|
//plate->pattern_file = it->second->pattern_file;
|
||||||
plate->no_light_thumbnail_file = it->second->no_light_thumbnail_file;
|
plate->no_light_thumbnail_file = it->second->no_light_thumbnail_file;
|
||||||
plate->top_file = it->second->top_file;
|
plate->top_file = it->second->top_file;
|
||||||
plate->pick_file = it->second->pick_file;
|
plate->pick_file = it->second->pick_file.empty();
|
||||||
plate->pattern_bbox_file = it->second->pattern_bbox_file;
|
plate->pattern_bbox_file = it->second->pattern_bbox_file.empty();
|
||||||
plate->config = it->second->config;
|
plate->config = it->second->config;
|
||||||
|
|
||||||
if (!plate->thumbnail_file.empty())
|
if (!plate->thumbnail_file.empty())
|
||||||
@@ -2091,13 +2073,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
else if (_is_svg_shape_file(name)) {
|
else if (_is_svg_shape_file(name)) {
|
||||||
_extract_embossed_svg_shape_file(name, archive, stat);
|
_extract_embossed_svg_shape_file(name, archive, stat);
|
||||||
}
|
}
|
||||||
else if (boost::algorithm::istarts_with(name, TEXTURE_DISPLACEMENT_DIR)) {
|
|
||||||
std::string contents(stat.m_uncomp_size, '\0');
|
|
||||||
if (mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, contents.data(), stat.m_uncomp_size, 0))
|
|
||||||
m_texture_displacement_files.emplace(name, std::move(contents));
|
|
||||||
else
|
|
||||||
add_error("Error while reading texture displacement data");
|
|
||||||
}
|
|
||||||
else if (!dont_load_config && boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) {
|
else if (!dont_load_config && boost::algorithm::iequals(name, SLICE_INFO_CONFIG_FILE)) {
|
||||||
m_parsing_slice_info = true;
|
m_parsing_slice_info = true;
|
||||||
//extract slice info from archive
|
//extract slice info from archive
|
||||||
@@ -3335,45 +3310,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
// Restores one volume's texture displacement stack from the JSON it named. Called while the volume is
|
|
||||||
// being built, which is after the whole archive has been walked - so every file it can refer to is
|
|
||||||
// already extracted, and no deferral is needed.
|
|
||||||
//
|
|
||||||
// A missing or malformed JSON leaves the volume with no layers but keeps the paint mask it already
|
|
||||||
// loaded, which is the same state as a project saved by a build without the feature: recoverable by
|
|
||||||
// picking the texture again rather than a hard failure.
|
|
||||||
void _BBS_3MF_Importer::_apply_texture_displacement(ModelVolume &volume, const std::string &json_path)
|
|
||||||
{
|
|
||||||
const auto json = m_texture_displacement_files.find(json_path);
|
|
||||||
if (json == m_texture_displacement_files.end()) {
|
|
||||||
add_error("Missing texture displacement data: " + json_path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::vector<TextureDisplacementLayer> layers;
|
|
||||||
TextureDisplacementOptions options;
|
|
||||||
if (!texture_displacement_layers_from_json(json->second, layers, options)) {
|
|
||||||
add_error("Malformed texture displacement data: " + json_path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// The archive is the only source of a layer's pixels. `path` comes from the project file, so loading
|
|
||||||
// the project must never read the file it names: that would let whoever made the project pick any
|
|
||||||
// local (or, on Windows, network) file to read, and the next save would embed it in the archive.
|
|
||||||
// `path` stays a label of where the texture came from; a layer the archive carries no image for
|
|
||||||
// loads without one, its settings intact, until the user picks a texture again.
|
|
||||||
for (TextureDisplacementLayer &layer : layers) {
|
|
||||||
if (layer.path_in_3mf.empty())
|
|
||||||
continue;
|
|
||||||
const auto image = m_texture_displacement_files.find(layer.path_in_3mf);
|
|
||||||
if (image == m_texture_displacement_files.end()) {
|
|
||||||
add_error("Missing texture displacement image: " + layer.path_in_3mf);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
layer.image_data = std::make_shared<std::vector<unsigned char>>(image->second.begin(), image->second.end());
|
|
||||||
}
|
|
||||||
volume.texture_displacement_layers = std::move(layers);
|
|
||||||
volume.texture_displacement_options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _BBS_3MF_Importer::_extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat){
|
void _BBS_3MF_Importer::_extract_embossed_svg_shape_file(const std::string &filename, mz_zip_archive &archive, const mz_zip_archive_file_stat &stat){
|
||||||
assert(m_path_to_emboss_shape_files.find(filename) == m_path_to_emboss_shape_files.end());
|
assert(m_path_to_emboss_shape_files.find(filename) == m_path_to_emboss_shape_files.end());
|
||||||
auto file = std::make_unique<std::string>(stat.m_uncomp_size, '\0');
|
auto file = std::make_unique<std::string>(stat.m_uncomp_size, '\0');
|
||||||
@@ -4030,12 +3966,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
m_curr_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
|
m_curr_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
|
||||||
m_curr_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
|
m_curr_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
|
||||||
m_curr_object->geometry.fuzzy_skin.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_FUZZY_SKIN_ATTR));
|
m_curr_object->geometry.fuzzy_skin.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_FUZZY_SKIN_ATTR));
|
||||||
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++slot)
|
|
||||||
if (const char *paint = bbs_get_attribute_value_charptr(attributes, num_attributes, TEXTURE_DISPLACEMENT_ATTRS[slot])) {
|
|
||||||
std::vector<std::string> &mask = m_curr_object->geometry.texture_displacement[slot];
|
|
||||||
mask.resize(m_curr_object->geometry.triangles.size());
|
|
||||||
mask.back() = paint;
|
|
||||||
}
|
|
||||||
// BBS
|
// BBS
|
||||||
m_curr_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR));
|
m_curr_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR));
|
||||||
}
|
}
|
||||||
@@ -5387,18 +5317,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
volume->mmu_segmentation_facets.touch();
|
volume->mmu_segmentation_facets.touch();
|
||||||
volume->fuzzy_skin_facets.shrink_to_fit();
|
volume->fuzzy_skin_facets.shrink_to_fit();
|
||||||
volume->fuzzy_skin_facets.touch();
|
volume->fuzzy_skin_facets.touch();
|
||||||
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++slot) {
|
|
||||||
const std::vector<std::string> &mask = sub_object->geometry.texture_displacement[slot];
|
|
||||||
if (mask.empty())
|
|
||||||
continue; // no triangle painted on this slot
|
|
||||||
FacetsAnnotation &facets = volume->texture_displacement_facet(slot);
|
|
||||||
facets.reserve(triangles_count);
|
|
||||||
for (size_t i = 0; i < triangles_count && i < mask.size(); ++i)
|
|
||||||
if (!mask[i].empty())
|
|
||||||
facets.set_triangle_from_string(i, mask[i]);
|
|
||||||
facets.shrink_to_fit();
|
|
||||||
facets.touch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
volume->set_type(volume_data->part_type);
|
volume->set_type(volume_data->part_type);
|
||||||
@@ -5414,8 +5332,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
for (const Metadata& metadata : volume_data->metadata) {
|
for (const Metadata& metadata : volume_data->metadata) {
|
||||||
if (metadata.key == NAME_KEY)
|
if (metadata.key == NAME_KEY)
|
||||||
volume->name = metadata.value;
|
volume->name = metadata.value;
|
||||||
else if (metadata.key == TEXTURE_DISPLACEMENT_KEY)
|
|
||||||
_apply_texture_displacement(*volume, metadata.value);
|
|
||||||
//else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
|
//else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
|
||||||
// volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
|
// volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
|
||||||
//for old format
|
//for old format
|
||||||
@@ -5580,28 +5496,12 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
volume->seam_facets.shrink_to_fit();
|
volume->seam_facets.shrink_to_fit();
|
||||||
volume->mmu_segmentation_facets.shrink_to_fit();
|
volume->mmu_segmentation_facets.shrink_to_fit();
|
||||||
|
|
||||||
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++slot) {
|
|
||||||
const std::vector<std::string> &mask = geometry.texture_displacement[slot];
|
|
||||||
if (mask.empty())
|
|
||||||
continue; // written by a build without the feature
|
|
||||||
FacetsAnnotation &facets = volume->texture_displacement_facet(slot);
|
|
||||||
facets.reserve(triangles_count);
|
|
||||||
for (size_t i = 0; i < triangles_count; ++i) {
|
|
||||||
const size_t index = volume_data.first_triangle_id + i;
|
|
||||||
if (index < mask.size() && !mask[index].empty())
|
|
||||||
facets.set_triangle_from_string(i, mask[index]);
|
|
||||||
}
|
|
||||||
facets.shrink_to_fit();
|
|
||||||
facets.touch();
|
|
||||||
}
|
|
||||||
volume->set_type(volume_data.part_type);
|
volume->set_type(volume_data.part_type);
|
||||||
|
|
||||||
// apply the remaining volume's metadata
|
// apply the remaining volume's metadata
|
||||||
for (const Metadata& metadata : volume_data.metadata) {
|
for (const Metadata& metadata : volume_data.metadata) {
|
||||||
if (metadata.key == NAME_KEY)
|
if (metadata.key == NAME_KEY)
|
||||||
volume->name = metadata.value;
|
volume->name = metadata.value;
|
||||||
else if (metadata.key == TEXTURE_DISPLACEMENT_KEY)
|
|
||||||
_apply_texture_displacement(*volume, metadata.value);
|
|
||||||
//else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
|
//else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
|
||||||
// volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
|
// volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
|
||||||
//for old format
|
//for old format
|
||||||
@@ -5905,12 +5805,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
current_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
|
current_object->geometry.custom_seam.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
|
||||||
current_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
|
current_object->geometry.mmu_segmentation.push_back(bbs_get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
|
||||||
current_object->geometry.fuzzy_skin.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_FUZZY_SKIN_ATTR));
|
current_object->geometry.fuzzy_skin.push_back(bbs_get_attribute_value_string(attributes, num_attributes, CUSTOM_FUZZY_SKIN_ATTR));
|
||||||
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++slot)
|
|
||||||
if (const char *paint = bbs_get_attribute_value_charptr(attributes, num_attributes, TEXTURE_DISPLACEMENT_ATTRS[slot])) {
|
|
||||||
std::vector<std::string> &mask = current_object->geometry.texture_displacement[slot];
|
|
||||||
mask.resize(current_object->geometry.triangles.size());
|
|
||||||
mask.back() = paint;
|
|
||||||
}
|
|
||||||
// BBS
|
// BBS
|
||||||
current_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR));
|
current_object->geometry.face_properties.push_back(bbs_get_attribute_value_string(attributes, num_attributes, FACE_PROPERTY_ATTR));
|
||||||
}
|
}
|
||||||
@@ -6914,9 +6808,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
stream << " <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n";
|
stream << " <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n";
|
||||||
stream << " <Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\"/>\n";
|
stream << " <Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\"/>\n";
|
||||||
stream << " <Default Extension=\"png\" ContentType=\"image/png\"/>\n";
|
stream << " <Default Extension=\"png\" ContentType=\"image/png\"/>\n";
|
||||||
stream << " <Default Extension=\"jpg\" ContentType=\"image/jpeg\"/>\n";
|
|
||||||
stream << " <Default Extension=\"jpeg\" ContentType=\"image/jpeg\"/>\n";
|
|
||||||
stream << " <Default Extension=\"json\" ContentType=\"application/json\"/>\n";
|
|
||||||
stream << " <Default Extension=\"gcode\" ContentType=\"text/x.gcode\"/>\n";
|
stream << " <Default Extension=\"gcode\" ContentType=\"text/x.gcode\"/>\n";
|
||||||
stream << "</Types>";
|
stream << "</Types>";
|
||||||
|
|
||||||
@@ -7341,14 +7232,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
if (iter != m_shared_meshes.end())
|
if (iter != m_shared_meshes.end())
|
||||||
{
|
{
|
||||||
const ModelVolume* shared_volume = iter->second.second;
|
const ModelVolume* shared_volume = iter->second.second;
|
||||||
bool same_texture_paint = true;
|
|
||||||
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS) && same_texture_paint; ++slot)
|
|
||||||
same_texture_paint = shared_volume->texture_displacement_facet(slot).equals(volume->texture_displacement_facet(slot));
|
|
||||||
if ((shared_volume->supported_facets.equals(volume->supported_facets))
|
if ((shared_volume->supported_facets.equals(volume->supported_facets))
|
||||||
&& (shared_volume->seam_facets.equals(volume->seam_facets))
|
&& (shared_volume->seam_facets.equals(volume->seam_facets))
|
||||||
&& (shared_volume->mmu_segmentation_facets.equals(volume->mmu_segmentation_facets))
|
&& (shared_volume->mmu_segmentation_facets.equals(volume->mmu_segmentation_facets))
|
||||||
&& (shared_volume->fuzzy_skin_facets.equals(volume->fuzzy_skin_facets))
|
&& (shared_volume->fuzzy_skin_facets.equals(volume->fuzzy_skin_facets)))
|
||||||
&& same_texture_paint)
|
|
||||||
{
|
{
|
||||||
auto data = iter->second.first;
|
auto data = iter->second.first;
|
||||||
const_cast<_BBS_3MF_Exporter *>(this)->m_volume_paths.insert({volume, {data->sub_path, data->volumes_objectID.find(iter->second.second)->second}});
|
const_cast<_BBS_3MF_Exporter *>(this)->m_volume_paths.insert({volume, {data->sub_path, data->volumes_objectID.find(iter->second.second)->second}});
|
||||||
@@ -7764,20 +7651,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
output_buffer += "\"";
|
output_buffer += "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
// One attribute per texture displacement layer slot. Older readers ignore attributes they do
|
|
||||||
// not know, so a project written here still opens in a build without the feature - it just
|
|
||||||
// loses the paint, which is also all it could have done with it.
|
|
||||||
for (int slot = 0; slot < TEXTURE_DISPLACEMENT_MAX_LAYERS; ++slot) {
|
|
||||||
const std::string texture_paint = volume->texture_displacement_facet(slot).get_triangle_as_string(i);
|
|
||||||
if (texture_paint.empty())
|
|
||||||
continue;
|
|
||||||
output_buffer += " ";
|
|
||||||
output_buffer += TEXTURE_DISPLACEMENT_ATTRS[slot];
|
|
||||||
output_buffer += "=\"";
|
|
||||||
output_buffer += texture_paint;
|
|
||||||
output_buffer += "\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
// BBS
|
// BBS
|
||||||
if (i < its.properties.size()) {
|
if (i < its.properties.size()) {
|
||||||
std::string prop_str = its.properties[i].to_string();
|
std::string prop_str = its.properties[i].to_string();
|
||||||
@@ -8227,42 +8100,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
|||||||
return file_path;
|
return file_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writes a volume's texture displacement stack into the archive and points the volume's metadata at it.
|
bool _BBS_3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config, int export_plate_idx, bool save_gcode, bool use_loaded_id)
|
||||||
// The layer settings go in as JSON and each layer's texture as the image file it was loaded from, both
|
|
||||||
// referenced by path - the same split EmbossShape makes for its SVG. The image deliberately stays out of
|
|
||||||
// the XML: it is binary and routinely megabytes, and base64 in an attribute would bloat the one file
|
|
||||||
// every reader has to parse just to list the objects.
|
|
||||||
static void add_texture_displacement(std::stringstream &stream, const ModelVolume &volume, mz_zip_archive &archive,
|
|
||||||
const std::string &id)
|
|
||||||
{
|
|
||||||
if (volume.texture_displacement_layers.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// A copy, because path_in_3mf is only meaningful inside the archive being written and the volume
|
|
||||||
// being exported is const (and may be saved again, elsewhere, with different paths).
|
|
||||||
std::vector<TextureDisplacementLayer> layers = volume.texture_displacement_layers;
|
|
||||||
for (TextureDisplacementLayer &layer : layers) {
|
|
||||||
layer.path_in_3mf.clear();
|
|
||||||
if (!layer.image_data || layer.image_data->empty())
|
|
||||||
continue;
|
|
||||||
// Always .png: the importer converts whatever the user picked (jpg, bmp, ...) to an 8-bit
|
|
||||||
// greyscale PNG, so image_data is PNG whatever layer.path happens to be named.
|
|
||||||
const std::string path = std::string(TEXTURE_DISPLACEMENT_DIR) + id + "_" + std::to_string(layer.slot) + ".png";
|
|
||||||
// No deflate: a PNG is already compressed, so a second pass only costs time.
|
|
||||||
if (mz_zip_writer_add_mem(&archive, path.c_str(), layer.image_data->data(), layer.image_data->size(),
|
|
||||||
MZ_NO_COMPRESSION))
|
|
||||||
layer.path_in_3mf = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string json = texture_displacement_layers_to_json(layers, volume.texture_displacement_options);
|
|
||||||
const std::string json_path = std::string(TEXTURE_DISPLACEMENT_DIR) + id + ".json";
|
|
||||||
if (!mz_zip_writer_add_mem(&archive, json_path.c_str(), json.data(), json.size(), MZ_DEFAULT_COMPRESSION))
|
|
||||||
return;
|
|
||||||
stream << " <" << METADATA_TAG << " " << KEY_ATTR << "=\"" << TEXTURE_DISPLACEMENT_KEY << "\" "
|
|
||||||
<< VALUE_ATTR << "=\"" << xml_escape(json_path) << "\"/>\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _BBS_3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config, int export_plate_idx, bool save_gcode, bool use_loaded_id)
|
|
||||||
{
|
{
|
||||||
std::stringstream stream;
|
std::stringstream stream;
|
||||||
// Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back
|
// Store mesh transformation in full precision, as the volumes are stored transformed and they need to be transformed back
|
||||||
@@ -8373,8 +8211,6 @@ bool _BBS_3MF_Exporter::_add_model_config_file_to_archive(mz_zip_archive& archiv
|
|||||||
if (const std::optional<EmbossShape> &es = volume->emboss_shape; es.has_value()) {
|
if (const std::optional<EmbossShape> &es = volume->emboss_shape; es.has_value()) {
|
||||||
to_xml(stream, *es, *volume, archive, m_fullpath_sources);
|
to_xml(stream, *es, *volume, archive, m_fullpath_sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_texture_displacement(stream, *volume, archive, std::to_string(volume->id().id));
|
|
||||||
|
|
||||||
if (const std::optional<TextConfiguration> &tc = volume->text_configuration;
|
if (const std::optional<TextConfiguration> &tc = volume->text_configuration;
|
||||||
tc.has_value())
|
tc.has_value())
|
||||||
|
|||||||
@@ -3295,7 +3295,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
|
|||||||
m_is_role_based_fan_on.fill(false);
|
m_is_role_based_fan_on.fill(false);
|
||||||
m_role_based_fan_marker_layer.fill(-1);
|
m_role_based_fan_marker_layer.fill(-1);
|
||||||
|
|
||||||
m_fan_mover.reset();
|
m_fan_mover.release();
|
||||||
m_ordering_cache.clear();
|
m_ordering_cache.clear();
|
||||||
|
|
||||||
// Belt printer: initialize belt-specific writer via virtual hook.
|
// Belt printer: initialize belt-specific writer via virtual hook.
|
||||||
|
|||||||
@@ -0,0 +1,349 @@
|
|||||||
|
#include "JumpPointSearch.hpp"
|
||||||
|
#include "BoundingBox.hpp"
|
||||||
|
#include "ExPolygon.hpp"
|
||||||
|
#include "Point.hpp"
|
||||||
|
#include "libslic3r/AStar.hpp"
|
||||||
|
#include "libslic3r/KDTreeIndirect.hpp"
|
||||||
|
#include "libslic3r/Polygon.hpp"
|
||||||
|
#include "libslic3r/Polyline.hpp"
|
||||||
|
#include "libslic3r/libslic3r.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iterator>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <oneapi/tbb/scalable_allocator.h>
|
||||||
|
|
||||||
|
//#define DEBUG_FILES
|
||||||
|
#ifdef DEBUG_FILES
|
||||||
|
#include "libslic3r/SVG.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// execute fn for each pixel on the line. If fn returns false, terminate the iteration
|
||||||
|
template<typename PointFn> void dda(coord_t x0, coord_t y0, coord_t x1, coord_t y1, const PointFn &fn)
|
||||||
|
{
|
||||||
|
coord_t dx = abs(x1 - x0);
|
||||||
|
coord_t dy = abs(y1 - y0);
|
||||||
|
coord_t x = x0;
|
||||||
|
coord_t y = y0;
|
||||||
|
coord_t n = 1 + dx + dy;
|
||||||
|
coord_t x_inc = (x1 > x0) ? 1 : -1;
|
||||||
|
coord_t y_inc = (y1 > y0) ? 1 : -1;
|
||||||
|
coord_t error = dx - dy;
|
||||||
|
dx *= 2;
|
||||||
|
dy *= 2;
|
||||||
|
|
||||||
|
for (; n > 0; --n) {
|
||||||
|
if (!fn(x, y)) return;
|
||||||
|
|
||||||
|
if (error > 0) {
|
||||||
|
x += x_inc;
|
||||||
|
error -= dy;
|
||||||
|
} else {
|
||||||
|
y += y_inc;
|
||||||
|
error += dx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// will draw the line twice, second time with and offset of 1 in the direction of normal
|
||||||
|
// may call the fn on the same coordiantes multiple times!
|
||||||
|
template<typename PointFn> void double_dda_with_offset(coord_t x0, coord_t y0, coord_t x1, coord_t y1, const PointFn &fn)
|
||||||
|
{
|
||||||
|
Vec2d normal = Point{y1 - y0, x1 - x0}.cast<double>().normalized();
|
||||||
|
normal.x() = ceil(normal.x());
|
||||||
|
normal.y() = ceil(normal.y());
|
||||||
|
Point start_offset = Point(x0, y0) + (normal).cast<coord_t>();
|
||||||
|
Point end_offset = Point(x1, y1) + (normal).cast<coord_t>();
|
||||||
|
|
||||||
|
dda(x0, y0, x1, y1, fn);
|
||||||
|
dda(start_offset.x(), start_offset.y(), end_offset.x(), end_offset.y(), fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename CellPositionType, typename CellQueryFn> class JPSTracer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Use incoming_dir [0,0] for starting points, so that all directions are checked from that point
|
||||||
|
struct Node
|
||||||
|
{
|
||||||
|
CellPositionType position;
|
||||||
|
CellPositionType incoming_dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
JPSTracer(CellPositionType target, CellQueryFn is_passable) : target(target), is_passable(is_passable) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
CellPositionType target;
|
||||||
|
CellQueryFn is_passable; // should return boolean whether the cell is passable or not
|
||||||
|
|
||||||
|
CellPositionType find_jump_point(CellPositionType start, CellPositionType forward_dir) const
|
||||||
|
{
|
||||||
|
CellPositionType next = start + forward_dir;
|
||||||
|
while (next != target && is_passable(next) && !(is_jump_point(next, forward_dir))) { next = next + forward_dir; }
|
||||||
|
|
||||||
|
if (is_passable(next)) {
|
||||||
|
return next;
|
||||||
|
} else {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_jump_point(CellPositionType pos, CellPositionType forward_dir) const
|
||||||
|
{
|
||||||
|
if (abs(forward_dir.x()) + abs(forward_dir.y()) == 2) {
|
||||||
|
// diagonal
|
||||||
|
CellPositionType horizontal_check_dir = CellPositionType{forward_dir.x(), 0};
|
||||||
|
CellPositionType vertical_check_dir = CellPositionType{0, forward_dir.y()};
|
||||||
|
|
||||||
|
if (!is_passable(pos - horizontal_check_dir) && is_passable(pos + forward_dir - 2 * horizontal_check_dir)) { return true; }
|
||||||
|
|
||||||
|
if (!is_passable(pos - vertical_check_dir) && is_passable(pos + forward_dir - 2 * vertical_check_dir)) { return true; }
|
||||||
|
|
||||||
|
if (find_jump_point(pos, horizontal_check_dir) != pos) { return true; }
|
||||||
|
|
||||||
|
if (find_jump_point(pos, vertical_check_dir) != pos) { return true; }
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} else { // horizontal or vertical
|
||||||
|
CellPositionType side_dir = CellPositionType(forward_dir.y(), forward_dir.x());
|
||||||
|
|
||||||
|
if (!is_passable(pos + side_dir) && is_passable(pos + forward_dir + side_dir)) { return true; }
|
||||||
|
|
||||||
|
if (!is_passable(pos - side_dir) && is_passable(pos + forward_dir - side_dir)) { return true; }
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
template<class Fn> void foreach_reachable(const Node &from, Fn &&fn) const
|
||||||
|
{
|
||||||
|
const CellPositionType & pos = from.position;
|
||||||
|
const CellPositionType & forward_dir = from.incoming_dir;
|
||||||
|
std::vector<CellPositionType> dirs_to_check{};
|
||||||
|
|
||||||
|
if (abs(forward_dir.x()) + abs(forward_dir.y()) == 0) { // special case for starting point
|
||||||
|
dirs_to_check = all_directions;
|
||||||
|
} else if (abs(forward_dir.x()) + abs(forward_dir.y()) == 2) {
|
||||||
|
// diagonal
|
||||||
|
CellPositionType horizontal_check_dir = CellPositionType{forward_dir.x(), 0};
|
||||||
|
CellPositionType vertical_check_dir = CellPositionType{0, forward_dir.y()};
|
||||||
|
|
||||||
|
if (!is_passable(pos - horizontal_check_dir) && is_passable(pos + forward_dir - 2 * horizontal_check_dir)) {
|
||||||
|
dirs_to_check.push_back(forward_dir - 2 * horizontal_check_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_passable(pos - vertical_check_dir) && is_passable(pos + forward_dir - 2 * vertical_check_dir)) {
|
||||||
|
dirs_to_check.push_back(forward_dir - 2 * vertical_check_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs_to_check.push_back(horizontal_check_dir);
|
||||||
|
dirs_to_check.push_back(vertical_check_dir);
|
||||||
|
dirs_to_check.push_back(forward_dir);
|
||||||
|
|
||||||
|
} else { // horizontal or vertical
|
||||||
|
CellPositionType side_dir = CellPositionType(forward_dir.y(), forward_dir.x());
|
||||||
|
|
||||||
|
if (!is_passable(pos + side_dir) && is_passable(pos + forward_dir + side_dir)) { dirs_to_check.push_back(forward_dir + side_dir); }
|
||||||
|
|
||||||
|
if (!is_passable(pos - side_dir) && is_passable(pos + forward_dir - side_dir)) { dirs_to_check.push_back(forward_dir - side_dir); }
|
||||||
|
dirs_to_check.push_back(forward_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const CellPositionType &dir : dirs_to_check) {
|
||||||
|
CellPositionType jp = find_jump_point(pos, dir);
|
||||||
|
if (jp != pos) fn(Node{jp, dir});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float distance(Node a, Node b) const { return (a.position - b.position).template cast<double>().norm(); }
|
||||||
|
|
||||||
|
float goal_heuristic(Node n) const { return n.position == target ? -1.f : (target - n.position).template cast<double>().norm(); }
|
||||||
|
|
||||||
|
size_t unique_id(Node n) const { return (static_cast<size_t>(uint16_t(n.position.x())) << 16) + static_cast<size_t>(uint16_t(n.position.y())); }
|
||||||
|
|
||||||
|
const std::vector<CellPositionType> all_directions{{1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, {0, -1}, {1, -1}};
|
||||||
|
};
|
||||||
|
|
||||||
|
void JPSPathFinder::clear()
|
||||||
|
{
|
||||||
|
inpassable.clear();
|
||||||
|
max_search_box.max = Pixel(std::numeric_limits<coord_t>::min(), std::numeric_limits<coord_t>::min());
|
||||||
|
max_search_box.min = Pixel(std::numeric_limits<coord_t>::max(), std::numeric_limits<coord_t>::max());
|
||||||
|
add_obstacles(bed_shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JPSPathFinder::add_obstacles(const Lines &obstacles)
|
||||||
|
{
|
||||||
|
auto store_obstacle = [&](coord_t x, coord_t y) {
|
||||||
|
max_search_box.max.x() = std::max(max_search_box.max.x(), x);
|
||||||
|
max_search_box.max.y() = std::max(max_search_box.max.y(), y);
|
||||||
|
max_search_box.min.x() = std::min(max_search_box.min.x(), x);
|
||||||
|
max_search_box.min.y() = std::min(max_search_box.min.y(), y);
|
||||||
|
inpassable.insert(Pixel{x, y});
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const Line &l : obstacles) {
|
||||||
|
Pixel start = pixelize(l.a);
|
||||||
|
Pixel end = pixelize(l.b);
|
||||||
|
double_dda_with_offset(start.x(), start.y(), end.x(), end.y(), store_obstacle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Polyline JPSPathFinder::find_path(const Point &p0, const Point &p1)
|
||||||
|
{
|
||||||
|
Pixel start = pixelize(p0);
|
||||||
|
Pixel end = pixelize(p1);
|
||||||
|
if (inpassable.empty() || (start - end).cast<float>().norm() < 3.0) { return Polyline{p0, p1}; }
|
||||||
|
|
||||||
|
if (inpassable.find(start) != inpassable.end()) {
|
||||||
|
dda(start.x(), start.y(), end.x(), end.y(), [&](coord_t x, coord_t y) {
|
||||||
|
if (inpassable.find(Pixel(x, y)) == inpassable.end() || start == end) { // new start not found yet, and xy passable
|
||||||
|
start = Pixel(x, y);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inpassable.find(end) != inpassable.end()) {
|
||||||
|
dda(end.x(), end.y(), start.x(), start.y(), [&](coord_t x, coord_t y) {
|
||||||
|
if (inpassable.find(Pixel(x, y)) == inpassable.end() || start == end) { // new start not found yet, and xy passable
|
||||||
|
end = Pixel(x, y);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundingBox search_box = max_search_box;
|
||||||
|
search_box.max -= Pixel(1, 1);
|
||||||
|
search_box.min += Pixel(1, 1);
|
||||||
|
|
||||||
|
BoundingBox bounding_square(Points{start, end});
|
||||||
|
bounding_square.max += Pixel(5, 5);
|
||||||
|
bounding_square.min -= Pixel(5, 5);
|
||||||
|
coord_t bounding_square_size = 2 * std::max(bounding_square.size().x(), bounding_square.size().y());
|
||||||
|
bounding_square.max.x() += (bounding_square_size - bounding_square.size().x()) / 2;
|
||||||
|
bounding_square.min.x() -= (bounding_square_size - bounding_square.size().x()) / 2;
|
||||||
|
bounding_square.max.y() += (bounding_square_size - bounding_square.size().y()) / 2;
|
||||||
|
bounding_square.min.y() -= (bounding_square_size - bounding_square.size().y()) / 2;
|
||||||
|
|
||||||
|
// Intersection - limit the search box to a square area around the start and end, to fasten the path searching
|
||||||
|
search_box.max = search_box.max.cwiseMin(bounding_square.max);
|
||||||
|
search_box.min = search_box.min.cwiseMax(bounding_square.min);
|
||||||
|
|
||||||
|
auto cell_query = [&](Pixel pixel) { return search_box.contains(pixel) && (pixel == start || pixel == end || inpassable.find(pixel) == inpassable.end()); };
|
||||||
|
|
||||||
|
JPSTracer<Pixel, decltype(cell_query)> tracer(end, cell_query);
|
||||||
|
using QNode = astar::QNode<JPSTracer<Pixel, decltype(cell_query)>>;
|
||||||
|
|
||||||
|
std::unordered_map<size_t, QNode> astar_cache{};
|
||||||
|
std::vector<Pixel, PointsAllocator<Pixel>> out_path;
|
||||||
|
std::vector<decltype(tracer)::Node> out_nodes;
|
||||||
|
|
||||||
|
if (!astar::search_route(tracer, {start, {0, 0}}, std::back_inserter(out_nodes), astar_cache)) {
|
||||||
|
// path not found - just reconstruct the best path from astar cache.
|
||||||
|
// Note that astar_cache is NOT empty - at least the starting point should always be there
|
||||||
|
auto coordiante_func = [&astar_cache](size_t idx, size_t dim) { return float(astar_cache[idx].node.position[dim]); };
|
||||||
|
std::vector<size_t> keys;
|
||||||
|
keys.reserve(astar_cache.size());
|
||||||
|
for (const auto &pair : astar_cache) { keys.push_back(pair.first); }
|
||||||
|
KDTreeIndirect<2, float, decltype(coordiante_func)> kd_tree(coordiante_func, keys);
|
||||||
|
size_t closest_qnode = find_closest_point(kd_tree, end.cast<float>());
|
||||||
|
|
||||||
|
out_path.push_back(end);
|
||||||
|
while (closest_qnode != astar::Unassigned) {
|
||||||
|
out_path.push_back(astar_cache[closest_qnode].node.position);
|
||||||
|
closest_qnode = astar_cache[closest_qnode].parent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const auto &node : out_nodes) { out_path.push_back(node.position); }
|
||||||
|
out_path.push_back(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_FILES
|
||||||
|
auto scaled_points = [](const Points &ps) {
|
||||||
|
Points r;
|
||||||
|
for (const Point &p : ps) { r.push_back(Point::new_scale(p.x(), p.y())); }
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
auto scaled_point = [](const Point &p) { return Point::new_scale(p.x(), p.y()); };
|
||||||
|
::Slic3r::SVG svg(debug_out_path(("path_jps" + std::to_string(print_z) + "_" + std::to_string(rand() % 1000)).c_str()).c_str(),
|
||||||
|
BoundingBox(scaled_point(search_box.min), scaled_point(search_box.max)));
|
||||||
|
for (const auto &p : inpassable) { svg.draw(scaled_point(p), "black", scale_(0.4)); }
|
||||||
|
for (const auto &qn : astar_cache) { svg.draw(scaled_point(qn.second.node.position), "blue", scale_(0.3)); }
|
||||||
|
svg.draw(Polyline(scaled_points(out_path)), "yellow", scale_(0.25));
|
||||||
|
svg.draw(scaled_point(end), "purple", scale_(0.4));
|
||||||
|
svg.draw(scaled_point(start), "green", scale_(0.4));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::vector<Pixel, PointsAllocator<Pixel>> tmp_path;
|
||||||
|
tmp_path.reserve(out_path.size());
|
||||||
|
// Some path found, reverse and remove points that do not change direction
|
||||||
|
std::reverse(out_path.begin(), out_path.end());
|
||||||
|
{
|
||||||
|
tmp_path.push_back(out_path.front()); // first point
|
||||||
|
for (size_t i = 1; i < out_path.size() - 1; i++) {
|
||||||
|
if ((out_path[i] - out_path[i - 1]).cast<float>().normalized() != (out_path[i + 1] - out_path[i]).cast<float>().normalized()) { tmp_path.push_back(out_path[i]); }
|
||||||
|
}
|
||||||
|
tmp_path.push_back(out_path.back()); // last_point
|
||||||
|
out_path = tmp_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_FILES
|
||||||
|
svg.draw(Polyline(scaled_points(out_path)), "orange", scale_(0.20));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
tmp_path.clear();
|
||||||
|
// remove redundant jump points - there are points that change direction but are not needed - this inefficiency arises from the
|
||||||
|
// usage of grid search The removal alg tries to find the longest Px Px+k path without obstacles. If Px Px+k+1 is blocked, it will
|
||||||
|
// insert the Px+k point to result and continue search from Px+k
|
||||||
|
{
|
||||||
|
tmp_path.push_back(out_path.front()); // first point
|
||||||
|
size_t index_of_last_stored_point = 0;
|
||||||
|
for (size_t i = 1; i < out_path.size(); i++) {
|
||||||
|
if (i - index_of_last_stored_point < 2) continue;
|
||||||
|
bool passable = true;
|
||||||
|
auto store_obstacle = [&](coord_t x, coord_t y) {
|
||||||
|
if (Pixel(x, y) != start && Pixel(x, y) != end && inpassable.find(Pixel(x, y)) != inpassable.end()) {
|
||||||
|
passable = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
dda(tmp_path.back().x(), tmp_path.back().y(), out_path[i].x(), out_path[i].y(), store_obstacle);
|
||||||
|
if (!passable) {
|
||||||
|
tmp_path.push_back(out_path[i - 1]);
|
||||||
|
index_of_last_stored_point = i - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tmp_path.push_back(out_path.back()); // last_point
|
||||||
|
out_path = tmp_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_FILES
|
||||||
|
svg.draw(Polyline(scaled_points(out_path)), "red", scale_(0.15));
|
||||||
|
svg.Close();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// before returing the path, transform it from pixels back to points.
|
||||||
|
// Also replace the first and last pixel by input points so that result path patches input params exactly.
|
||||||
|
for (Pixel &p : out_path) { p = unpixelize(p); }
|
||||||
|
out_path.front() = p0;
|
||||||
|
out_path.back() = p1;
|
||||||
|
|
||||||
|
return Polyline(out_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Line.hpp"
|
||||||
|
#ifndef SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_
|
||||||
|
#define SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_
|
||||||
|
|
||||||
|
#include "BoundingBox.hpp"
|
||||||
|
#include "Polygon.hpp"
|
||||||
|
#include "libslic3r/Layer.hpp"
|
||||||
|
#include "libslic3r/Point.hpp"
|
||||||
|
#include "libslic3r/Polyline.hpp"
|
||||||
|
#include "libslic3r/libslic3r.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class JPSPathFinder
|
||||||
|
{
|
||||||
|
using Pixel = Point;
|
||||||
|
std::unordered_set<Pixel, PointHash> inpassable;
|
||||||
|
coordf_t print_z;
|
||||||
|
BoundingBox max_search_box;
|
||||||
|
Lines bed_shape;
|
||||||
|
|
||||||
|
const coord_t resolution = scaled(1.5);
|
||||||
|
Pixel pixelize(const Point &p) { return p / resolution; }
|
||||||
|
Point unpixelize(const Pixel &p) { return p * resolution; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
JPSPathFinder() = default;
|
||||||
|
void init_bed_shape(const Points &bed_shape) { this->bed_shape = (to_lines(Polygon{bed_shape})); };
|
||||||
|
void clear();
|
||||||
|
void add_obstacles(const Lines &obstacles);
|
||||||
|
Polyline find_path(const Point &start, const Point &end);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif /* SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_ */
|
||||||
@@ -2991,15 +2991,6 @@ void PrintObject::clear_shared_object()
|
|||||||
void PrintObject::copy_layers_from_shared_object()
|
void PrintObject::copy_layers_from_shared_object()
|
||||||
{
|
{
|
||||||
if (m_shared_object) {
|
if (m_shared_object) {
|
||||||
// Orca: Print::process() calls this once per slice for each sharing object, after clear_shared_object() has
|
|
||||||
// emptied any earlier alias, so the vectors hold only layers this object sliced itself before it started sharing
|
|
||||||
// (or nothing). Free them here, as clear_support_layers() / clear_layers() would; those skip deletion while
|
|
||||||
// m_shared_object is set. The belt-only data they also free is always empty here, because belt printers never
|
|
||||||
// share layers.
|
|
||||||
for (SupportLayer *layer : m_support_layers)
|
|
||||||
delete layer;
|
|
||||||
for (Layer *layer : m_layers)
|
|
||||||
delete layer;
|
|
||||||
m_layers.clear();
|
m_layers.clear();
|
||||||
m_support_layers.clear();
|
m_support_layers.clear();
|
||||||
|
|
||||||
|
|||||||
@@ -982,9 +982,6 @@ void PrintObject::generate_support_material(bool with_belt_brim)
|
|||||||
this->_generate_support_material();
|
this->_generate_support_material();
|
||||||
m_print->throw_if_canceled();
|
m_print->throw_if_canceled();
|
||||||
}
|
}
|
||||||
// Orca: the tree support collision/avoidance caches and support nodes are only used while this step runs
|
|
||||||
// (detect_overhangs() rebuilds them from scratch), so don't keep them resident until the next slice.
|
|
||||||
this->clear_tree_support_preview_cache();
|
|
||||||
// Belt brim rides here rather than in the brim step because its apron
|
// Belt brim rides here rather than in the brim step because its apron
|
||||||
// prologue introduces print_z values below the object's first layer, and
|
// prologue introduces print_z values below the object's first layer, and
|
||||||
// those must exist before ToolOrdering is built at psWipeTower - one step
|
// those must exist before ToolOrdering is built at psWipeTower - one step
|
||||||
|
|||||||
@@ -1532,7 +1532,7 @@ void PrintObject::slice_volumes()
|
|||||||
if (min_growth < 0.f || elfoot > 0.f) {
|
if (min_growth < 0.f || elfoot > 0.f) {
|
||||||
// Apply the negative XY compensation. (the ones that is <0)
|
// Apply the negative XY compensation. (the ones that is <0)
|
||||||
ExPolygons trimming;
|
ExPolygons trimming;
|
||||||
const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5);
|
static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5);
|
||||||
if (elfoot > 0.f) {
|
if (elfoot > 0.f) {
|
||||||
ExPolygons expolygons_to_compensate = offset_ex(layer->merged(eps), -eps);
|
ExPolygons expolygons_to_compensate = offset_ex(layer->merged(eps), -eps);
|
||||||
lslices_elfoot_uncompensated[layer_id] = expolygons_to_compensate;
|
lslices_elfoot_uncompensated[layer_id] = expolygons_to_compensate;
|
||||||
|
|||||||
@@ -1976,15 +1976,37 @@ coordf_t TreeSupport::get_radius(const SupportNode* node)
|
|||||||
return node->radius;
|
return node->radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Orca: drop_nodes() calls these several times per node per layer, so they return a reference into the
|
ExPolygons TreeSupport::get_avoidance(coordf_t radius, size_t obj_layer_nr)
|
||||||
// TreeSupportData cache. The cache only ever inserts, so its entries never move while nodes drop in parallel.
|
|
||||||
const ExPolygons& TreeSupport::get_avoidance(coordf_t radius, size_t obj_layer_nr)
|
|
||||||
{
|
{
|
||||||
|
#if USE_SUPPORT_3D
|
||||||
|
if (m_model_volumes) {
|
||||||
|
bool on_build_plate = m_object_config->support_on_build_plate_only.value;
|
||||||
|
const Polygons& avoid_polys = m_model_volumes->getAvoidance(radius, obj_layer_nr, TreeSupport3D::TreeModelVolumes::AvoidanceType::FastSafe, on_build_plate, true);
|
||||||
|
ExPolygons expolys;
|
||||||
|
for (auto& poly : avoid_polys)
|
||||||
|
expolys.emplace_back(std::move(poly));
|
||||||
|
return expolys;
|
||||||
|
}
|
||||||
|
return ExPolygons();
|
||||||
|
#else
|
||||||
return m_ts_data->get_avoidance(radius, obj_layer_nr);
|
return m_ts_data->get_avoidance(radius, obj_layer_nr);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
const ExPolygons& TreeSupport::get_collision(coordf_t radius, size_t layer_nr)
|
ExPolygons TreeSupport::get_collision(coordf_t radius, size_t layer_nr)
|
||||||
{
|
{
|
||||||
|
#if USE_SUPPORT_3D
|
||||||
|
if (m_model_volumes) {
|
||||||
|
bool on_build_plate = m_object_config->support_on_build_plate_only.value;
|
||||||
|
const Polygons& collision_polys = m_model_volumes->getCollision(radius, layer_nr, true);
|
||||||
|
ExPolygons expolys;
|
||||||
|
for (auto& poly : collision_polys)
|
||||||
|
expolys.emplace_back(std::move(poly));
|
||||||
|
return expolys;
|
||||||
|
}
|
||||||
|
#else
|
||||||
return m_ts_data->get_collision(radius, layer_nr);
|
return m_ts_data->get_collision(radius, layer_nr);
|
||||||
|
#endif
|
||||||
|
return ExPolygons();
|
||||||
}
|
}
|
||||||
Polygons TreeSupport::get_collision_polys(coordf_t radius, size_t layer_nr)
|
Polygons TreeSupport::get_collision_polys(coordf_t radius, size_t layer_nr)
|
||||||
{
|
{
|
||||||
@@ -2805,12 +2827,7 @@ void TreeSupport::draw_circles()
|
|||||||
#endif // SUPPORT_TREE_DEBUG_TO_SVG
|
#endif // SUPPORT_TREE_DEBUG_TO_SVG
|
||||||
|
|
||||||
SupportLayerPtrs& ts_layers = m_object->support_layers();
|
SupportLayerPtrs& ts_layers = m_object->support_layers();
|
||||||
// Orca: the vector owns its layers, so the dropped ones are deleted before they are erased.
|
auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](SupportLayer* ts_layer) { return ts_layer->height < EPSILON; });
|
||||||
// std::stable_partition (unlike std::remove_if) leaves exactly the dropped layers in the tail.
|
|
||||||
// Only heights below EPSILON are dropped; written as !(height < EPSILON), a NaN height is kept.
|
|
||||||
auto iter = std::stable_partition(ts_layers.begin(), ts_layers.end(), [](SupportLayer* ts_layer) { return !(ts_layer->height < EPSILON); });
|
|
||||||
for (auto it = iter; it != ts_layers.end(); ++it)
|
|
||||||
delete *it;
|
|
||||||
ts_layers.erase(iter, ts_layers.end());
|
ts_layers.erase(iter, ts_layers.end());
|
||||||
for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) {
|
for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) {
|
||||||
ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr;
|
ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr;
|
||||||
@@ -3054,7 +3071,7 @@ void TreeSupport::drop_nodes()
|
|||||||
//Insert a completely new node and let both original nodes fade.
|
//Insert a completely new node and let both original nodes fade.
|
||||||
Point next_position = (node.position + neighbours[0]) / 2; //Average position of the two nodes.
|
Point next_position = (node.position + neighbours[0]) / 2; //Average position of the two nodes.
|
||||||
coordf_t next_radius = calc_radius(node.dist_mm_to_top+height_next);
|
coordf_t next_radius = calc_radius(node.dist_mm_to_top+height_next);
|
||||||
const ExPolygons& avoid_layer = get_avoidance(next_radius, obj_layer_nr_next);
|
auto avoid_layer = get_avoidance(next_radius, obj_layer_nr_next);
|
||||||
if (group_index == 0)
|
if (group_index == 0)
|
||||||
{
|
{
|
||||||
//Avoid collisions.
|
//Avoid collisions.
|
||||||
@@ -3258,7 +3275,7 @@ void TreeSupport::drop_nodes()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
coordf_t next_radius = calc_radius(node.dist_mm_to_top + height_next);
|
coordf_t next_radius = calc_radius(node.dist_mm_to_top + height_next);
|
||||||
const ExPolygons& avoidance_next = get_avoidance(next_radius, obj_layer_nr_next);
|
auto avoidance_next = get_avoidance(next_radius, obj_layer_nr_next);
|
||||||
|
|
||||||
Point to_outside = projection_onto(avoidance_next, node.position);
|
Point to_outside = projection_onto(avoidance_next, node.position);
|
||||||
Point direction_to_outer = to_outside - node.position;
|
Point direction_to_outer = to_outside - node.position;
|
||||||
@@ -3320,7 +3337,7 @@ void TreeSupport::drop_nodes()
|
|||||||
p_node->to_buildplate = false;
|
p_node->to_buildplate = false;
|
||||||
return; // from parallel_for_each lambda
|
return; // from parallel_for_each lambda
|
||||||
}
|
}
|
||||||
const ExPolygons& next_collision = get_collision(0, obj_layer_nr_next);
|
auto next_collision = get_collision(0, obj_layer_nr_next);
|
||||||
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
|
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[obj_layer_nr_next], next_layer_vertex);
|
||||||
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
|
// don't increase radius if next node will collide partially with the object (STUDIO-7883)
|
||||||
to_outside = projection_onto(next_collision, next_layer_vertex);
|
to_outside = projection_onto(next_collision, next_layer_vertex);
|
||||||
|
|||||||
@@ -536,9 +536,9 @@ private:
|
|||||||
coordf_t calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor, bool use_min_distance=true);
|
coordf_t calc_branch_radius(coordf_t base_radius, coordf_t mm_to_top, double diameter_angle_scale_factor, bool use_min_distance=true);
|
||||||
coordf_t calc_radius(coordf_t mm_to_top);
|
coordf_t calc_radius(coordf_t mm_to_top);
|
||||||
coordf_t get_radius(const SupportNode* node);
|
coordf_t get_radius(const SupportNode* node);
|
||||||
const ExPolygons& get_avoidance(coordf_t radius, size_t obj_layer_nr);
|
ExPolygons get_avoidance(coordf_t radius, size_t obj_layer_nr);
|
||||||
// layer's expolygon expanded by radius+m_xy_distance
|
// layer's expolygon expanded by radius+m_xy_distance
|
||||||
const ExPolygons& get_collision(coordf_t radius, size_t layer_nr);
|
ExPolygons get_collision(coordf_t radius, size_t layer_nr);
|
||||||
// get Polygons instead of ExPolygons
|
// get Polygons instead of ExPolygons
|
||||||
Polygons get_collision_polys(coordf_t radius, size_t layer_nr);
|
Polygons get_collision_polys(coordf_t radius, size_t layer_nr);
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include "nlohmann/json.hpp"
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <ratio>
|
#include <ratio>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -5167,260 +5166,5 @@ indexed_triangle_set cut_mesh_at_steps(const indexed_triangle_set &mesh, const s
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
|
||||||
// Project persistence. See the declarations in TextureDisplacement.hpp for why this is JSON rather
|
|
||||||
// than the cereal stream next to them.
|
|
||||||
// ---------------------------------------------------------------------------------------------
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
nlohmann::json vec2_to_json(const Vec2f &v) { return nlohmann::json::array({ v.x(), v.y() }); }
|
|
||||||
nlohmann::json vec3_to_json(const Vec3f &v) { return nlohmann::json::array({ v.x(), v.y(), v.z() }); }
|
|
||||||
|
|
||||||
Vec2f vec2_from_json(const nlohmann::json &j, const Vec2f &fallback)
|
|
||||||
{
|
|
||||||
if (!j.is_array() || j.size() != 2)
|
|
||||||
return fallback;
|
|
||||||
return Vec2f(j[0].get<float>(), j[1].get<float>());
|
|
||||||
}
|
|
||||||
Vec3f vec3_from_json(const nlohmann::json &j, const Vec3f &fallback)
|
|
||||||
{
|
|
||||||
if (!j.is_array() || j.size() != 3)
|
|
||||||
return fallback;
|
|
||||||
return Vec3f(j[0].get<float>(), j[1].get<float>(), j[2].get<float>());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads key `k` into `out` when it is present and of the expected type, leaving `out` alone otherwise.
|
|
||||||
// That "leave it alone" is the whole point: every member starts at its struct default, so a project
|
|
||||||
// written by an older build simply keeps the defaults for whatever it did not know about.
|
|
||||||
template<class T> void read(const nlohmann::json &j, const char *k, T &out)
|
|
||||||
{
|
|
||||||
const auto it = j.find(k);
|
|
||||||
if (it == j.end())
|
|
||||||
return;
|
|
||||||
try {
|
|
||||||
out = it->get<T>();
|
|
||||||
} catch (...) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void read_enum(const nlohmann::json &j, const char *k, int &out)
|
|
||||||
{
|
|
||||||
const auto it = j.find(k);
|
|
||||||
if (it != j.end() && it->is_number_integer())
|
|
||||||
out = it->get<int>();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
std::string texture_displacement_layers_to_json(const std::vector<TextureDisplacementLayer> &layers,
|
|
||||||
const TextureDisplacementOptions &options)
|
|
||||||
{
|
|
||||||
nlohmann::json root;
|
|
||||||
root["version"] = 1;
|
|
||||||
|
|
||||||
nlohmann::json &opt = root["options"];
|
|
||||||
opt["displace_border"] = options.displace_border;
|
|
||||||
opt["smooth_enabled"] = options.smooth_enabled;
|
|
||||||
opt["smooth_strength"] = options.smooth_strength;
|
|
||||||
opt["smooth_iterations"] = options.smooth_iterations;
|
|
||||||
opt["smooth_skip_border"] = options.smooth_skip_border;
|
|
||||||
opt["pipeline_v2"] = options.pipeline_v2;
|
|
||||||
opt["v2_refine_mm"] = options.v2_refine_mm;
|
|
||||||
opt["v2_regularize"] = options.v2_regularize;
|
|
||||||
opt["v2_max_triangles_k"] = options.v2_max_triangles_k;
|
|
||||||
opt["v2_relocate"] = options.v2_relocate;
|
|
||||||
opt["v2_flip_edges"] = options.v2_flip_edges;
|
|
||||||
opt["color_mix_enabled"] = options.color_mix_enabled;
|
|
||||||
opt["color_despeckle"] = options.color_despeckle;
|
|
||||||
opt["color_mix_count"] = options.color_mix_count;
|
|
||||||
opt["active_slot"] = options.active_slot;
|
|
||||||
opt["panel_mode"] = options.panel_mode;
|
|
||||||
|
|
||||||
nlohmann::json &arr = root["layers"];
|
|
||||||
arr = nlohmann::json::array();
|
|
||||||
for (const TextureDisplacementLayer &l : layers) {
|
|
||||||
nlohmann::json j;
|
|
||||||
j["slot"] = l.slot;
|
|
||||||
j["name"] = l.name;
|
|
||||||
j["path"] = l.path;
|
|
||||||
j["path_in_3mf"] = l.path_in_3mf;
|
|
||||||
|
|
||||||
j["depth_mm"] = l.depth_mm;
|
|
||||||
j["tiling_scale"] = l.tiling_scale;
|
|
||||||
j["rotation_deg"] = l.rotation_deg;
|
|
||||||
j["offset"] = vec2_to_json(l.offset);
|
|
||||||
j["invert"] = l.invert;
|
|
||||||
j["midlevel"] = l.midlevel;
|
|
||||||
j["smoothing"] = l.smoothing;
|
|
||||||
|
|
||||||
j["edge_smoothing"] = l.edge_smoothing;
|
|
||||||
j["edge_smoothing_amount"] = l.edge_smoothing_amount;
|
|
||||||
j["auto_connect_islands"] = l.auto_connect_islands;
|
|
||||||
|
|
||||||
j["tile_enabled"] = l.tile_enabled;
|
|
||||||
j["tile_method"] = int(l.tile_method);
|
|
||||||
j["projection_method"] = int(l.projection_method);
|
|
||||||
j["blend_mode"] = int(l.blend_mode);
|
|
||||||
j["color_enabled"] = l.color_enabled;
|
|
||||||
|
|
||||||
j["lscm_seam_angle_deg"] = l.lscm_seam_angle_deg;
|
|
||||||
j["island_padding_mm"] = l.island_padding_mm;
|
|
||||||
|
|
||||||
j["view_project_right"] = vec3_to_json(l.view_project_right);
|
|
||||||
j["view_project_up"] = vec3_to_json(l.view_project_up);
|
|
||||||
j["view_project_projective"] = l.view_project_projective;
|
|
||||||
j["view_project_matrix"] = l.view_project_matrix;
|
|
||||||
|
|
||||||
// Flat pairs rather than nested arrays: shorter, and the reader can simply ignore a trailing
|
|
||||||
// odd element instead of having to validate every sub-array.
|
|
||||||
nlohmann::json &seams = j["lscm_seam_edges"];
|
|
||||||
seams = nlohmann::json::array();
|
|
||||||
for (const auto &[a, b] : l.lscm_seam_edges) {
|
|
||||||
seams.push_back(a);
|
|
||||||
seams.push_back(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
nlohmann::json &islands = j["islands"];
|
|
||||||
islands = nlohmann::json::array();
|
|
||||||
for (const TextureIsland &island : l.islands)
|
|
||||||
islands.push_back(nlohmann::json::array(
|
|
||||||
{ island.offset.x(), island.offset.y(), island.rotation_deg, island.scale }));
|
|
||||||
|
|
||||||
j["island_groups"] = l.island_groups;
|
|
||||||
|
|
||||||
nlohmann::json &overrides = j["lscm_uv_overrides"];
|
|
||||||
overrides = nlohmann::json::array();
|
|
||||||
for (const auto &[key, uv] : l.lscm_uv_overrides)
|
|
||||||
overrides.push_back(nlohmann::json::array({ key, uv.x(), uv.y() }));
|
|
||||||
|
|
||||||
arr.push_back(std::move(j));
|
|
||||||
}
|
|
||||||
return root.dump();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool texture_displacement_layers_from_json(const std::string &text,
|
|
||||||
std::vector<TextureDisplacementLayer> &layers,
|
|
||||||
TextureDisplacementOptions &options)
|
|
||||||
{
|
|
||||||
nlohmann::json root;
|
|
||||||
try {
|
|
||||||
root = nlohmann::json::parse(text);
|
|
||||||
} catch (...) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!root.is_object())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
TextureDisplacementOptions out_options;
|
|
||||||
if (const auto it = root.find("options"); it != root.end() && it->is_object()) {
|
|
||||||
const nlohmann::json &opt = *it;
|
|
||||||
read(opt, "displace_border", out_options.displace_border);
|
|
||||||
read(opt, "smooth_enabled", out_options.smooth_enabled);
|
|
||||||
read(opt, "smooth_strength", out_options.smooth_strength);
|
|
||||||
read(opt, "smooth_iterations", out_options.smooth_iterations);
|
|
||||||
read(opt, "smooth_skip_border", out_options.smooth_skip_border);
|
|
||||||
read(opt, "pipeline_v2", out_options.pipeline_v2);
|
|
||||||
read(opt, "v2_refine_mm", out_options.v2_refine_mm);
|
|
||||||
read(opt, "v2_regularize", out_options.v2_regularize);
|
|
||||||
read(opt, "v2_max_triangles_k", out_options.v2_max_triangles_k);
|
|
||||||
read(opt, "v2_relocate", out_options.v2_relocate);
|
|
||||||
read(opt, "v2_flip_edges", out_options.v2_flip_edges);
|
|
||||||
read(opt, "color_mix_enabled", out_options.color_mix_enabled);
|
|
||||||
read(opt, "color_despeckle", out_options.color_despeckle);
|
|
||||||
read(opt, "color_mix_count", out_options.color_mix_count);
|
|
||||||
read(opt, "active_slot", out_options.active_slot);
|
|
||||||
read(opt, "panel_mode", out_options.panel_mode);
|
|
||||||
out_options.active_slot = std::clamp(out_options.active_slot, 0, int(TEXTURE_DISPLACEMENT_MAX_LAYERS) - 1);
|
|
||||||
out_options.panel_mode = std::clamp(out_options.panel_mode, 0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<TextureDisplacementLayer> out_layers;
|
|
||||||
// read() skips a value of the wrong type, but the vector, matrix, seam, island and UV-override arrays
|
|
||||||
// below read their numbers with get<>(), which throws on one; that rejects the document, like text that
|
|
||||||
// does not parse.
|
|
||||||
try {
|
|
||||||
if (const auto it = root.find("layers"); it != root.end() && it->is_array()) {
|
|
||||||
for (const nlohmann::json &j : *it) {
|
|
||||||
if (!j.is_object())
|
|
||||||
continue;
|
|
||||||
TextureDisplacementLayer l;
|
|
||||||
read(j, "slot", l.slot);
|
|
||||||
// The panel and the paint masks are indexed by slot, so a layer without a valid one rejects the
|
|
||||||
// document rather than reaching them.
|
|
||||||
if (l.slot < 0 || l.slot >= int(TEXTURE_DISPLACEMENT_MAX_LAYERS))
|
|
||||||
return false;
|
|
||||||
read(j, "name", l.name);
|
|
||||||
read(j, "path", l.path);
|
|
||||||
read(j, "path_in_3mf", l.path_in_3mf);
|
|
||||||
|
|
||||||
read(j, "depth_mm", l.depth_mm);
|
|
||||||
read(j, "tiling_scale", l.tiling_scale);
|
|
||||||
read(j, "rotation_deg", l.rotation_deg);
|
|
||||||
if (const auto o = j.find("offset"); o != j.end())
|
|
||||||
l.offset = vec2_from_json(*o, l.offset);
|
|
||||||
read(j, "invert", l.invert);
|
|
||||||
read(j, "midlevel", l.midlevel);
|
|
||||||
read(j, "smoothing", l.smoothing);
|
|
||||||
|
|
||||||
read(j, "edge_smoothing", l.edge_smoothing);
|
|
||||||
read(j, "edge_smoothing_amount", l.edge_smoothing_amount);
|
|
||||||
read(j, "auto_connect_islands", l.auto_connect_islands);
|
|
||||||
|
|
||||||
read(j, "tile_enabled", l.tile_enabled);
|
|
||||||
read(j, "color_enabled", l.color_enabled);
|
|
||||||
int tile_method = int(l.tile_method), projection = int(l.projection_method), blend = int(l.blend_mode);
|
|
||||||
read_enum(j, "tile_method", tile_method);
|
|
||||||
read_enum(j, "projection_method", projection);
|
|
||||||
read_enum(j, "blend_mode", blend);
|
|
||||||
l.tile_method = TextureTileMethod(tile_method);
|
|
||||||
l.projection_method = TextureProjectionMethod(std::clamp(projection, 0, 4));
|
|
||||||
l.blend_mode = TextureBlendMode(blend);
|
|
||||||
|
|
||||||
read(j, "lscm_seam_angle_deg", l.lscm_seam_angle_deg);
|
|
||||||
read(j, "island_padding_mm", l.island_padding_mm);
|
|
||||||
|
|
||||||
if (const auto v = j.find("view_project_right"); v != j.end())
|
|
||||||
l.view_project_right = vec3_from_json(*v, l.view_project_right);
|
|
||||||
if (const auto v = j.find("view_project_up"); v != j.end())
|
|
||||||
l.view_project_up = vec3_from_json(*v, l.view_project_up);
|
|
||||||
read(j, "view_project_projective", l.view_project_projective);
|
|
||||||
if (const auto v = j.find("view_project_matrix"); v != j.end() && v->is_array() && v->size() == 12)
|
|
||||||
for (size_t i = 0; i < 12; ++i)
|
|
||||||
l.view_project_matrix[i] = (*v)[i].get<float>();
|
|
||||||
|
|
||||||
if (const auto v = j.find("lscm_seam_edges"); v != j.end() && v->is_array())
|
|
||||||
for (size_t i = 0; i + 1 < v->size(); i += 2)
|
|
||||||
l.lscm_seam_edges.emplace_back((*v)[i].get<int>(), (*v)[i + 1].get<int>());
|
|
||||||
|
|
||||||
if (const auto v = j.find("islands"); v != j.end() && v->is_array())
|
|
||||||
for (const nlohmann::json &e : *v) {
|
|
||||||
if (!e.is_array() || e.size() != 4)
|
|
||||||
continue;
|
|
||||||
TextureIsland island;
|
|
||||||
island.offset = Vec2f(e[0].get<float>(), e[1].get<float>());
|
|
||||||
island.rotation_deg = e[2].get<float>();
|
|
||||||
island.scale = e[3].get<float>();
|
|
||||||
l.islands.push_back(island);
|
|
||||||
}
|
|
||||||
|
|
||||||
read(j, "island_groups", l.island_groups);
|
|
||||||
|
|
||||||
if (const auto v = j.find("lscm_uv_overrides"); v != j.end() && v->is_array())
|
|
||||||
for (const nlohmann::json &e : *v) {
|
|
||||||
if (!e.is_array() || e.size() != 3)
|
|
||||||
continue;
|
|
||||||
l.lscm_uv_overrides.emplace_back(e[0].get<int>(), Vec2f(e[1].get<float>(), e[2].get<float>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
out_layers.push_back(std::move(l));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (const nlohmann::json::exception &) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
layers = std::move(out_layers);
|
|
||||||
options = out_options;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -397,41 +397,14 @@ struct TextureDisplacementOptions
|
|||||||
// finer than the mesh leaves behind, without eating features that are genuinely a facet wide.
|
// finer than the mesh leaves behind, without eating features that are genuinely a facet wide.
|
||||||
int color_despeckle = 2;
|
int color_despeckle = 2;
|
||||||
|
|
||||||
// Where the panel was left, rather than anything the bake reads. It lives with the volume because
|
|
||||||
// that is what it describes: reopening a project should put the user back on the layer they were
|
|
||||||
// editing, in the mode they were editing it in, not on slot 0 in Standard.
|
|
||||||
int active_slot = 0;
|
|
||||||
int panel_mode = 0; // 0 Standard, 1 Pro
|
|
||||||
|
|
||||||
template<class Archive> void serialize(Archive &ar)
|
template<class Archive> void serialize(Archive &ar)
|
||||||
{
|
{
|
||||||
ar(displace_border, smooth_enabled, smooth_strength, smooth_iterations, smooth_skip_border,
|
ar(displace_border, smooth_enabled, smooth_strength, smooth_iterations, smooth_skip_border,
|
||||||
pipeline_v2, v2_refine_mm, v2_regularize, v2_max_triangles_k,
|
pipeline_v2, v2_refine_mm, v2_regularize, v2_max_triangles_k,
|
||||||
v2_relocate, v2_flip_edges, color_mix_enabled, color_despeckle, color_mix_count,
|
v2_relocate, color_mix_enabled, color_despeckle, color_mix_count);
|
||||||
active_slot, panel_mode);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Project persistence (see bbs_3mf.cpp). The layer stack and the per-volume options are written to the
|
|
||||||
// .3mf as JSON rather than through the cereal save()/load() above: those two are positional and
|
|
||||||
// unversioned, which is right for the undo/redo stack they serve (one session, one binary) but would
|
|
||||||
// make every future field a project-breaking change. A JSON object tolerates both directions - an
|
|
||||||
// unknown key is ignored, a missing one keeps the member's default - so old projects keep loading and
|
|
||||||
// new ones degrade gracefully in older builds.
|
|
||||||
//
|
|
||||||
// The texture image itself is *not* in here. It is a binary blob that belongs in the archive as a file
|
|
||||||
// of its own, exactly as EmbossShape stores its SVG; `path_in_3mf` names that file, and the caller is
|
|
||||||
// responsible for writing it and for filling `image_data` back in on load.
|
|
||||||
std::string texture_displacement_layers_to_json(const std::vector<TextureDisplacementLayer> &layers,
|
|
||||||
const TextureDisplacementOptions &options);
|
|
||||||
// Returns false and leaves both outputs untouched when the text is not a valid JSON object, a number in a
|
|
||||||
// layer's vector, matrix, seam, island or UV-override array has the wrong type, or a layer's slot is
|
|
||||||
// missing or outside [0, TEXTURE_DISPLACEMENT_MAX_LAYERS). Any other value of the wrong type or shape
|
|
||||||
// keeps its default.
|
|
||||||
bool texture_displacement_layers_from_json(const std::string &text,
|
|
||||||
std::vector<TextureDisplacementLayer> &layers,
|
|
||||||
TextureDisplacementOptions &options);
|
|
||||||
|
|
||||||
// How much detail a height texture carries: central differences of the grey image, the mean gradient
|
// How much detail a height texture carries: central differences of the grey image, the mean gradient
|
||||||
// and the share of texels steeper than 30 grey levels, mapped to how many texels one mesh edge may
|
// and the share of texels steeper than 30 grey levels, mapped to how many texels one mesh edge may
|
||||||
// span (1 for a hard-edged image, 4 for a smooth one). Cached per image, like the decode.
|
// span (1 for a hard-edged image, 4 for a smooth one). Cached per image, like the decode.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#ifndef libslic3r_Timer_hpp_
|
||||||
|
#define libslic3r_Timer_hpp_
|
||||||
|
|
||||||
#include "TriangleMesh.hpp"
|
#include "TriangleMesh.hpp"
|
||||||
|
|
||||||
@@ -9,3 +10,5 @@ public:
|
|||||||
static TriangleMesh smooth_triangle_mesh(const TriangleMesh &mesh,bool& ok);
|
static TriangleMesh smooth_triangle_mesh(const TriangleMesh &mesh,bool& ok);
|
||||||
};
|
};
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // libslic3r_Timer_hpp_
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ extern std::string format_memsize_MB(size_t n);
|
|||||||
extern std::string format_memsize(size_t bytes, unsigned int decimals = 1);
|
extern std::string format_memsize(size_t bytes, unsigned int decimals = 1);
|
||||||
// Return string to be added to the boost::log output to inform about the current process memory allocation.
|
// Return string to be added to the boost::log output to inform about the current process memory allocation.
|
||||||
// The string is non-empty if the loglevel >= info (3) or ignore_loglevel==true.
|
// The string is non-empty if the loglevel >= info (3) or ignore_loglevel==true.
|
||||||
|
// Latter is used to get the memory info from SysInfoDialog.
|
||||||
extern std::string log_memory_info(bool ignore_loglevel = false);
|
extern std::string log_memory_info(bool ignore_loglevel = false);
|
||||||
extern void disable_multi_threading();
|
extern void disable_multi_threading();
|
||||||
// Returns the size of physical memory (RAM) in bytes.
|
// Returns the size of physical memory (RAM) in bytes.
|
||||||
|
|||||||
@@ -1744,7 +1744,9 @@ std::string format_diameter_to_str(double diameter, int precision)
|
|||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns platform-specific string to be used as log output.
|
// Returns platform-specific string to be used as log output or parsed in SysInfoDialog.
|
||||||
|
// The latter parses the string with (semi)colons as separators, it should look about as
|
||||||
|
// "desc1: value1; desc2: value2" or similar (spaces should not matter).
|
||||||
std::string log_memory_info(bool ignore_loglevel)
|
std::string log_memory_info(bool ignore_loglevel)
|
||||||
{
|
{
|
||||||
std::string out;
|
std::string out;
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ namespace libvgcode {
|
|||||||
// the triangles that share it: the shader runs 8 times per segment instead of 24.
|
// the triangles that share it: the shader runs 8 times per segment instead of 24.
|
||||||
static constexpr const std::array<uint8_t, 8> VERTEX_DATA = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
static constexpr const std::array<uint8_t, 8> VERTEX_DATA = { 0, 1, 2, 3, 4, 5, 6, 7 };
|
||||||
|
|
||||||
// 16-bit indices: GCN 1/2 GPUs have no 8-bit index support, and some of their drivers
|
static constexpr const std::array<uint8_t, 24> INDEX_DATA = {
|
||||||
// emulate it wrongly in instanced draws.
|
|
||||||
static constexpr const std::array<uint16_t, 24> INDEX_DATA = {
|
|
||||||
0, 1, 2, // front spike
|
0, 1, 2, // front spike
|
||||||
0, 2, 3, // front spike
|
0, 2, 3, // front spike
|
||||||
0, 3, 4, // right/bottom body
|
0, 3, 4, // right/bottom body
|
||||||
@@ -39,7 +37,7 @@ void SegmentTemplate::init()
|
|||||||
if (m_vao_id != 0)
|
if (m_vao_id != 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_size_in_bytes_gpu += VERTEX_DATA.size() * sizeof(uint8_t) + INDEX_DATA.size() * sizeof(uint16_t);
|
m_size_in_bytes_gpu += (VERTEX_DATA.size() + INDEX_DATA.size()) * sizeof(uint8_t);
|
||||||
|
|
||||||
int curr_vertex_array;
|
int curr_vertex_array;
|
||||||
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
|
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
|
||||||
@@ -63,7 +61,7 @@ void SegmentTemplate::init()
|
|||||||
// and restored together with the vao.
|
// and restored together with the vao.
|
||||||
glsafe(glGenBuffers(1, &m_ibo_id));
|
glsafe(glGenBuffers(1, &m_ibo_id));
|
||||||
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id));
|
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id));
|
||||||
glsafe(glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_DATA.size() * sizeof(uint16_t), INDEX_DATA.data(), GL_STATIC_DRAW));
|
glsafe(glBufferData(GL_ELEMENT_ARRAY_BUFFER, INDEX_DATA.size() * sizeof(uint8_t), INDEX_DATA.data(), GL_STATIC_DRAW));
|
||||||
|
|
||||||
glsafe(glBindBuffer(GL_ARRAY_BUFFER, curr_array_buffer));
|
glsafe(glBindBuffer(GL_ARRAY_BUFFER, curr_array_buffer));
|
||||||
glsafe(glBindVertexArray(curr_vertex_array));
|
glsafe(glBindVertexArray(curr_vertex_array));
|
||||||
@@ -96,7 +94,7 @@ void SegmentTemplate::render(size_t count)
|
|||||||
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
|
glsafe(glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &curr_vertex_array));
|
||||||
|
|
||||||
glsafe(glBindVertexArray(m_vao_id));
|
glsafe(glBindVertexArray(m_vao_id));
|
||||||
glsafe(glDrawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(INDEX_DATA.size()), GL_UNSIGNED_SHORT,
|
glsafe(glDrawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(INDEX_DATA.size()), GL_UNSIGNED_BYTE,
|
||||||
nullptr, static_cast<GLsizei>(count)));
|
nullptr, static_cast<GLsizei>(count)));
|
||||||
glsafe(glBindVertexArray(curr_vertex_array));
|
glsafe(glBindVertexArray(curr_vertex_array));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -416,6 +416,8 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
GUI/MultiSendMachineModel.hpp
|
GUI/MultiSendMachineModel.hpp
|
||||||
GUI/MultiTaskManagerPage.cpp
|
GUI/MultiTaskManagerPage.cpp
|
||||||
GUI/MultiTaskManagerPage.hpp
|
GUI/MultiTaskManagerPage.hpp
|
||||||
|
GUI/MultiTaskModel.cpp
|
||||||
|
GUI/MultiTaskModel.hpp
|
||||||
GUI/NetworkTestDialog.cpp
|
GUI/NetworkTestDialog.cpp
|
||||||
GUI/NetworkTestDialog.hpp
|
GUI/NetworkTestDialog.hpp
|
||||||
GUI/Notebook.cpp
|
GUI/Notebook.cpp
|
||||||
@@ -800,6 +802,7 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
Utils/PrintHost.hpp
|
Utils/PrintHost.hpp
|
||||||
Utils/Process.cpp
|
Utils/Process.cpp
|
||||||
Utils/Process.hpp
|
Utils/Process.hpp
|
||||||
|
Utils/Profile.hpp
|
||||||
Utils/RaycastManager.cpp
|
Utils/RaycastManager.cpp
|
||||||
Utils/RaycastManager.hpp
|
Utils/RaycastManager.hpp
|
||||||
Utils/Repetier.cpp
|
Utils/Repetier.cpp
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
#include <wx/checklst.h>
|
#include <wx/checklst.h>
|
||||||
#include "slic3r/GUI/GUI_Utils.hpp"
|
#include "slic3r/GUI/GUI_Utils.hpp"
|
||||||
#include "slic3r/GUI/Widgets/Button.hpp"
|
#include "slic3r/GUI/Widgets/Button.hpp"
|
||||||
#include "slic3r/GUI/Widgets/DropDown.hpp"
|
|
||||||
#include "slic3r/GUI/Widgets/Label.hpp"
|
#include "slic3r/GUI/Widgets/Label.hpp"
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "slic3r/GUI/wxExtensions.hpp"
|
#include "slic3r/GUI/wxExtensions.hpp"
|
||||||
@@ -173,26 +172,7 @@ void AMSMaterialsSetting::create()
|
|||||||
|
|
||||||
Bind(wxEVT_PAINT, &AMSMaterialsSetting::paintEvent, this);
|
Bind(wxEVT_PAINT, &AMSMaterialsSetting::paintEvent, this);
|
||||||
Bind(EVT_SELECTED_COLOR, &AMSMaterialsSetting::on_picker_color, this);
|
Bind(EVT_SELECTED_COLOR, &AMSMaterialsSetting::on_picker_color, this);
|
||||||
m_comboBox_filament->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_filament), NULL, this);
|
m_comboBox_filament->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_filament), NULL, this);
|
||||||
m_comboBox_filament->Bind(EVT_DROPDOWN_ITEM_ACTION, &AMSMaterialsSetting::on_toggle_favorite_filament, this);
|
|
||||||
|
|
||||||
// Favorites toggled in the open list are regrouped just before it opens again, so rows never move
|
|
||||||
// under the cursor. These handlers run ahead of ComboBox's own, which then measure and show the
|
|
||||||
// rebuilt rows; wxEVT_COMBOBOX_DROPDOWN would be too late, as ComboBox sends it after showing the list.
|
|
||||||
auto regroup_favorites = [this]() {
|
|
||||||
if (!m_favorites_regroup || m_comboBox_filament->is_drop_down())
|
|
||||||
return;
|
|
||||||
const FilamentInfos* info = selected_filament_info();
|
|
||||||
const std::string filament_id = info ? info->filament_id : std::string();
|
|
||||||
rebuild_filament_dropdown_items(filament_id);
|
|
||||||
};
|
|
||||||
m_comboBox_filament->Bind(wxEVT_LEFT_DOWN, [regroup_favorites](wxMouseEvent& e) { regroup_favorites(); e.Skip(); });
|
|
||||||
m_comboBox_filament->Bind(wxEVT_LEFT_DCLICK, [regroup_favorites](wxMouseEvent& e) { regroup_favorites(); e.Skip(); });
|
|
||||||
m_comboBox_filament->Bind(wxEVT_KEY_DOWN, [regroup_favorites](wxKeyEvent& e) {
|
|
||||||
if (e.GetKeyCode() == WXK_RETURN || e.GetKeyCode() == WXK_SPACE) // the keys that open the list
|
|
||||||
regroup_favorites();
|
|
||||||
e.Skip();
|
|
||||||
});
|
|
||||||
|
|
||||||
m_comboBox_cali_result->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_cali_result), NULL, this);
|
m_comboBox_cali_result->Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_cali_result), NULL, this);
|
||||||
}
|
}
|
||||||
@@ -211,9 +191,11 @@ void AMSMaterialsSetting::create_panel_normal(wxWindow* parent)
|
|||||||
|
|
||||||
m_sizer_filament->Add(0, 0, 0, wxEXPAND, 0);
|
m_sizer_filament->Add(0, 0, 0, wxEXPAND, 0);
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
m_comboBox_filament = new wxComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, AMS_MATERIALS_SETTING_COMBOX_WIDTH, 0, nullptr, wxCB_READONLY);
|
||||||
|
#else
|
||||||
m_comboBox_filament = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, AMS_MATERIALS_SETTING_COMBOX_WIDTH, 0, nullptr, wxCB_READONLY);
|
m_comboBox_filament = new ::ComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, AMS_MATERIALS_SETTING_COMBOX_WIDTH, 0, nullptr, wxCB_READONLY);
|
||||||
m_star_filled = ScalableBitmap(this, "score_star_light", 16);
|
#endif
|
||||||
m_star_empty = ScalableBitmap(this, "score_star_outline", 16);
|
|
||||||
|
|
||||||
m_sizer_filament->Add(m_comboBox_filament, 1, wxALIGN_CENTER, 0);
|
m_sizer_filament->Add(m_comboBox_filament, 1, wxALIGN_CENTER, 0);
|
||||||
|
|
||||||
@@ -438,7 +420,6 @@ void AMSMaterialsSetting::paintEvent(wxPaintEvent &evt)
|
|||||||
AMSMaterialsSetting::~AMSMaterialsSetting()
|
AMSMaterialsSetting::~AMSMaterialsSetting()
|
||||||
{
|
{
|
||||||
m_comboBox_filament->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_filament), NULL, this);
|
m_comboBox_filament->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_filament), NULL, this);
|
||||||
m_comboBox_filament->Unbind(EVT_DROPDOWN_ITEM_ACTION, &AMSMaterialsSetting::on_toggle_favorite_filament, this);
|
|
||||||
m_comboBox_cali_result->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_cali_result), NULL, this);
|
m_comboBox_cali_result->Disconnect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(AMSMaterialsSetting::on_select_cali_result), NULL, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,9 +531,10 @@ void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
|
|||||||
std::string selected_ams_id;
|
std::string selected_ams_id;
|
||||||
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
|
PresetBundle *preset_bundle = wxGetApp().preset_bundle;
|
||||||
if (preset_bundle) {
|
if (preset_bundle) {
|
||||||
const FilamentInfos* selected_info = selected_filament_info();
|
|
||||||
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
|
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
|
||||||
if (selected_info && it->filament_id.compare(selected_info->filament_id) == 0) {
|
auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
|
||||||
|
std::string filament_id = filament_item.filament_id;
|
||||||
|
if (it->filament_id.compare(filament_id) == 0) {
|
||||||
selected_ams_id = it->filament_id;
|
selected_ams_id = it->filament_id;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -700,13 +682,12 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
|||||||
ams_setting_id = "";
|
ams_setting_id = "";
|
||||||
|
|
||||||
// the combobox item
|
// the combobox item
|
||||||
const FilamentInfos* selected_info = selected_filament_info();
|
auto filament_item = map_filament_items[m_comboBox_filament->GetValue().ToStdString()];
|
||||||
const std::string filament_id = selected_info ? selected_info->filament_id : std::string();
|
|
||||||
|
|
||||||
// check filament info (rack-gated per-nozzle blacklist evaluation)
|
// check filament info (rack-gated per-nozzle blacklist evaluation)
|
||||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||||
const auto& fila_check_res = sCheckFilamentInfo(preset_bundle, obj, ams_id, slot_id,
|
const auto& fila_check_res = sCheckFilamentInfo(preset_bundle, obj, ams_id, slot_id,
|
||||||
filament_id, ams_filament_id, ams_setting_id);
|
filament_item.filament_id, ams_filament_id, ams_setting_id);
|
||||||
|
|
||||||
if (const auto& prohibit_items = fila_check_res.get_items_by_action("prohibition"); !prohibit_items.empty()) {
|
if (const auto& prohibit_items = fila_check_res.get_items_by_action("prohibition"); !prohibit_items.empty()) {
|
||||||
wxString info_msg;
|
wxString info_msg;
|
||||||
@@ -1005,133 +986,6 @@ static void _collect_filament_info(const wxString& shown_name,
|
|||||||
query_filament_types[shown_name] = filament.config.get_filament_type();
|
query_filament_types[shown_name] = filament.config.get_filament_type();
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* AMS_FAVORITE_FILAMENT_IDS_KEY = "ams_favorite_filament_ids";
|
|
||||||
|
|
||||||
const AMSMaterialsSetting::FilamentInfos* AMSMaterialsSetting::selected_filament_info() const
|
|
||||||
{
|
|
||||||
const int selection = m_comboBox_filament->GetSelection();
|
|
||||||
if (selection < 0 || selection >= int(m_filament_list_items.size()) || !m_filament_list_items[selection].is_filament)
|
|
||||||
return nullptr;
|
|
||||||
return &m_filament_list_items[selection].filament;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<std::string> AMSMaterialsSetting::load_favorite_filament_ids() const
|
|
||||||
{
|
|
||||||
std::set<std::string> favorite_ids;
|
|
||||||
std::vector<std::string> values;
|
|
||||||
const std::string serialized = wxGetApp().app_config->get(AMS_FAVORITE_FILAMENT_IDS_KEY);
|
|
||||||
if (!serialized.empty() && Slic3r::unescape_strings_cstyle(serialized, values)) {
|
|
||||||
favorite_ids.insert(values.begin(), values.end());
|
|
||||||
}
|
|
||||||
return favorite_ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AMSMaterialsSetting::save_favorite_filament_ids(const std::set<std::string>& favorite_ids)
|
|
||||||
{
|
|
||||||
std::vector<std::string> values(favorite_ids.begin(), favorite_ids.end());
|
|
||||||
wxGetApp().app_config->set(AMS_FAVORITE_FILAMENT_IDS_KEY, Slic3r::escape_strings_cstyle(values));
|
|
||||||
wxGetApp().app_config->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AMSMaterialsSetting::rebuild_filament_dropdown_items(const std::string& selected_filament_id)
|
|
||||||
{
|
|
||||||
std::vector<DropDown::Item> items;
|
|
||||||
m_filament_list_items.clear();
|
|
||||||
|
|
||||||
auto append_header = [&items, this](const wxString& label) {
|
|
||||||
DropDown::Item item;
|
|
||||||
item.text = label;
|
|
||||||
item.style = DD_ITEM_STYLE_SPLIT_ITEM | DD_ITEM_STYLE_DISABLED;
|
|
||||||
items.push_back(item);
|
|
||||||
m_filament_list_items.push_back(FilamentListItem{});
|
|
||||||
};
|
|
||||||
|
|
||||||
auto append_filament = [&items, this](const wxString& name) {
|
|
||||||
auto info_it = map_filament_items.find(name);
|
|
||||||
if (info_it == map_filament_items.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
DropDown::Item item;
|
|
||||||
item.text = name;
|
|
||||||
items.push_back(item);
|
|
||||||
m_filament_list_items.push_back(FilamentListItem{info_it->second, true});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Favorites are listed again at the top under their own header. Without any, the list is the
|
|
||||||
// plain filament list.
|
|
||||||
const std::set<std::string> favorite_ids = load_favorite_filament_ids();
|
|
||||||
std::vector<wxString> favorites;
|
|
||||||
for (const wxString& name : m_sorted_filament_items) {
|
|
||||||
auto info_it = map_filament_items.find(name);
|
|
||||||
if (info_it != map_filament_items.end() && favorite_ids.count(info_it->second.filament_id) > 0)
|
|
||||||
favorites.push_back(name);
|
|
||||||
}
|
|
||||||
if (!favorites.empty()) {
|
|
||||||
append_header(_L("Favorite filaments"));
|
|
||||||
for (const wxString& name : favorites)
|
|
||||||
append_filament(name);
|
|
||||||
append_header(_L("All filaments"));
|
|
||||||
}
|
|
||||||
for (const wxString& name : m_sorted_filament_items)
|
|
||||||
append_filament(name);
|
|
||||||
|
|
||||||
int selection_idx = -1;
|
|
||||||
if (!selected_filament_id.empty()) {
|
|
||||||
for (int i = 0; i < int(m_filament_list_items.size()); ++i) {
|
|
||||||
if (m_filament_list_items[i].is_filament && m_filament_list_items[i].filament.filament_id == selected_filament_id) {
|
|
||||||
selection_idx = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear() first: SetItems() keeps the old selection, and SetSelection() ignores an index equal to it,
|
|
||||||
// which would leave the previous filament's name on the combo.
|
|
||||||
m_comboBox_filament->Clear();
|
|
||||||
m_comboBox_filament->SetItems(items);
|
|
||||||
update_favorite_stars();
|
|
||||||
m_comboBox_filament->SetSelection(selection_idx);
|
|
||||||
m_favorites_regroup = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AMSMaterialsSetting::update_favorite_stars()
|
|
||||||
{
|
|
||||||
const std::set<std::string> favorite_ids = load_favorite_filament_ids();
|
|
||||||
for (size_t i = 0; i < m_filament_list_items.size(); ++i) {
|
|
||||||
const FilamentListItem& row = m_filament_list_items[i];
|
|
||||||
if (row.is_filament) {
|
|
||||||
const bool favorite = favorite_ids.count(row.filament.filament_id) > 0;
|
|
||||||
m_comboBox_filament->SetItemActionBitmap(i, favorite ? m_star_filled.bmp() : m_star_empty.bmp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AMSMaterialsSetting::toggle_favorite_filament(const std::string& filament_id)
|
|
||||||
{
|
|
||||||
if (filament_id.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::set<std::string> favorite_ids = load_favorite_filament_ids();
|
|
||||||
auto iter = favorite_ids.find(filament_id);
|
|
||||||
if (iter == favorite_ids.end())
|
|
||||||
favorite_ids.insert(filament_id);
|
|
||||||
else
|
|
||||||
favorite_ids.erase(iter);
|
|
||||||
|
|
||||||
save_favorite_filament_ids(favorite_ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AMSMaterialsSetting::on_toggle_favorite_filament(wxCommandEvent& evt)
|
|
||||||
{
|
|
||||||
const int index = evt.GetInt();
|
|
||||||
if (index < 0 || index >= int(m_filament_list_items.size()) || !m_filament_list_items[index].is_filament)
|
|
||||||
return;
|
|
||||||
|
|
||||||
toggle_favorite_filament(m_filament_list_items[index].filament.filament_id);
|
|
||||||
update_favorite_stars();
|
|
||||||
m_favorites_regroup = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_min, wxString temp_max, wxString k, wxString n)
|
void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_min, wxString temp_max, wxString k, wxString n)
|
||||||
{
|
{
|
||||||
if (!obj) return;
|
if (!obj) return;
|
||||||
@@ -1145,12 +999,13 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
|
|||||||
m_input_k_val->GetTextCtrl()->SetValue(k);
|
m_input_k_val->GetTextCtrl()->SetValue(k);
|
||||||
m_input_n_val->GetTextCtrl()->SetValue(n);
|
m_input_n_val->GetTextCtrl()->SetValue(n);
|
||||||
|
|
||||||
|
wxArrayString filament_items;
|
||||||
wxString bambu_filament_name;
|
wxString bambu_filament_name;
|
||||||
|
wxString hint_filament_name; // the hint type to be selected
|
||||||
std::unordered_map<wxString, wxString> query_filament_vendors;// some information for sort
|
std::unordered_map<wxString, wxString> query_filament_vendors;// some information for sort
|
||||||
std::unordered_map<wxString, wxString> query_filament_types; //
|
std::unordered_map<wxString, wxString> query_filament_types; //
|
||||||
|
|
||||||
std::set<std::string> filament_id_set;
|
std::set<std::string> filament_id_set;
|
||||||
m_sorted_filament_items.clear();
|
|
||||||
// The alias keyed map has to start empty: it is a member, so a stale alias left by an earlier
|
// The alias keyed map has to start empty: it is a member, so a stale alias left by an earlier
|
||||||
// popup (a different printer, a different nozzle) would resolve to that printer's filament_id.
|
// popup (a different printer, a different nozzle) would resolve to that printer's filament_id.
|
||||||
map_filament_items.clear();
|
map_filament_items.clear();
|
||||||
@@ -1180,7 +1035,7 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
|
|||||||
if (alias.empty())
|
if (alias.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
m_sorted_filament_items.push_back(alias);
|
filament_items.push_back(alias);
|
||||||
_collect_filament_info(alias, *filament_it, query_filament_vendors, query_filament_types);
|
_collect_filament_info(alias, *filament_it, query_filament_vendors, query_filament_types);
|
||||||
|
|
||||||
FilamentInfos filament_infos;
|
FilamentInfos filament_infos;
|
||||||
@@ -1189,6 +1044,7 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
|
|||||||
map_filament_items[alias] = filament_infos;
|
map_filament_items[alias] = filament_infos;
|
||||||
|
|
||||||
if (filament_it->filament_id == ams_filament_id) {
|
if (filament_it->filament_id == ams_filament_id) {
|
||||||
|
hint_filament_name = from_u8(alias);
|
||||||
bambu_filament_name = from_u8(alias);
|
bambu_filament_name = from_u8(alias);
|
||||||
|
|
||||||
|
|
||||||
@@ -1359,11 +1215,24 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
|
|||||||
return name_compare != 0 ? name_compare < 0 : left < right;
|
return name_compare != 0 ? name_compare < 0 : left < right;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::sort(m_sorted_filament_items.begin(), m_sorted_filament_items.end(), _filament_sorter);
|
std::sort(filament_items.begin(), filament_items.end(), _filament_sorter);
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuild_filament_dropdown_items(ams_filament_id);
|
// traverse the hint selection idx
|
||||||
int selection_idx = m_comboBox_filament->GetSelection();
|
int selection_idx = -1;
|
||||||
|
{
|
||||||
|
for(int i = 0; i < filament_items.size(); i++)
|
||||||
|
{
|
||||||
|
if (hint_filament_name == filament_items[i])
|
||||||
|
{
|
||||||
|
selection_idx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_comboBox_filament->Set(filament_items);
|
||||||
|
m_comboBox_filament->SetSelection(selection_idx);
|
||||||
post_select_event(selection_idx);
|
post_select_event(selection_idx);
|
||||||
|
|
||||||
if (selection_idx < 0) {
|
if (selection_idx < 0) {
|
||||||
@@ -1420,7 +1289,6 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
|
|||||||
int* from_printer = static_cast<int*>(m_comboBox_filament->GetClientData());
|
int* from_printer = static_cast<int*>(m_comboBox_filament->GetClientData());
|
||||||
|
|
||||||
m_filament_type = "";
|
m_filament_type = "";
|
||||||
const FilamentInfos* selected_info = selected_filament_info();
|
|
||||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||||
if (preset_bundle) {
|
if (preset_bundle) {
|
||||||
std::ostringstream stream;
|
std::ostringstream stream;
|
||||||
@@ -1441,8 +1309,9 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
|
|||||||
std::string nozzle_diameter_str = stream.str();
|
std::string nozzle_diameter_str = stream.str();
|
||||||
// Resolve the selection against the same list Popup() built the dropdown from, so the two
|
// Resolve the selection against the same list Popup() built the dropdown from, so the two
|
||||||
// halves of the dialog cannot disagree about which filaments this machine can use.
|
// halves of the dialog cannot disagree about which filaments this machine can use.
|
||||||
if (selected_info) {
|
const std::string selected = m_comboBox_filament->GetValue().ToStdString();
|
||||||
const std::string& filament_id = selected_info->filament_id;
|
if (!selected.empty()) {
|
||||||
|
const std::string filament_id = map_filament_items[selected].filament_id;
|
||||||
for (Preset *it : preset_bundle->get_filament_presets_for_machine(
|
for (Preset *it : preset_bundle->get_filament_presets_for_machine(
|
||||||
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) {
|
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) {
|
||||||
if (it->filament_id != filament_id)
|
if (it->filament_id != filament_id)
|
||||||
@@ -1495,7 +1364,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
|
|||||||
//reset cali
|
//reset cali
|
||||||
int cali_select_idx = -1;
|
int cali_select_idx = -1;
|
||||||
|
|
||||||
if ( !this->obj || m_filament_selection < 0 || !selected_info) {
|
if ( !this->obj || m_filament_selection < 0) {
|
||||||
m_input_k_val->Enable(false);
|
m_input_k_val->Enable(false);
|
||||||
m_input_n_val->Enable(false);
|
m_input_n_val->Enable(false);
|
||||||
m_button_confirm->Disable(); // ORCA No need to change style
|
m_button_confirm->Disable(); // ORCA No need to change style
|
||||||
@@ -1514,9 +1383,21 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
|
|||||||
ams_filament_id = "";
|
ams_filament_id = "";
|
||||||
ams_setting_id = "";
|
ams_setting_id = "";
|
||||||
|
|
||||||
if (selected_info) {
|
if (preset_bundle) {
|
||||||
ams_filament_id = selected_info->filament_id;
|
for (auto it = preset_bundle->filaments.begin(); it != preset_bundle->filaments.end(); it++) {
|
||||||
ams_setting_id = selected_info->setting_id;
|
auto itor = map_filament_items.find(m_comboBox_filament->GetValue().ToStdString());
|
||||||
|
if ( itor != map_filament_items.end()) {
|
||||||
|
ams_filament_id = itor->second.filament_id;
|
||||||
|
ams_setting_id = itor->second.setting_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it->alias.compare(m_comboBox_filament->GetValue().ToStdString()) == 0) {
|
||||||
|
ams_filament_id = it->filament_id;
|
||||||
|
ams_setting_id = it->setting_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wxArrayString items;
|
wxArrayString items;
|
||||||
@@ -1648,11 +1529,6 @@ void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect)
|
|||||||
m_button_reset->Rescale(); // ORCA
|
m_button_reset->Rescale(); // ORCA
|
||||||
m_button_confirm->Rescale(); // ORCA
|
m_button_confirm->Rescale(); // ORCA
|
||||||
m_button_close->Rescale(); // ORCA
|
m_button_close->Rescale(); // ORCA
|
||||||
// An open list would keep the measurements of the old stars, so close it before replacing them.
|
|
||||||
m_comboBox_filament->CloseDropdown();
|
|
||||||
m_star_filled.msw_rescale();
|
|
||||||
m_star_empty.msw_rescale();
|
|
||||||
update_favorite_stars();
|
|
||||||
this->Refresh();
|
this->Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
#include "slic3r/GUI/Widgets/StateColor.hpp"
|
#include "slic3r/GUI/Widgets/StateColor.hpp"
|
||||||
#include <wx/panel.h>
|
#include <wx/panel.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
|
||||||
|
|
||||||
#define AMS_MATERIALS_SETTING_DEF_COLOUR wxColour(255, 255, 255)
|
#define AMS_MATERIALS_SETTING_DEF_COLOUR wxColour(255, 255, 255)
|
||||||
#define AMS_MATERIALS_SETTING_GREY900 wxColour(38, 46, 48)
|
#define AMS_MATERIALS_SETTING_GREY900 wxColour(38, 46, 48)
|
||||||
@@ -157,7 +156,6 @@ protected:
|
|||||||
void create_panel_kn(wxWindow* parent);
|
void create_panel_kn(wxWindow* parent);
|
||||||
void on_dpi_changed(const wxRect &suggested_rect) override;
|
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||||
void on_select_filament(wxCommandEvent& evt);
|
void on_select_filament(wxCommandEvent& evt);
|
||||||
void on_toggle_favorite_filament(wxCommandEvent& evt);
|
|
||||||
void on_select_cali_result(wxCommandEvent &evt);
|
void on_select_cali_result(wxCommandEvent &evt);
|
||||||
void on_select_ok(wxCommandEvent &event);
|
void on_select_ok(wxCommandEvent &event);
|
||||||
void on_select_reset(wxCommandEvent &event);
|
void on_select_reset(wxCommandEvent &event);
|
||||||
@@ -165,11 +163,6 @@ protected:
|
|||||||
void on_clr_picker(wxMouseEvent &event);
|
void on_clr_picker(wxMouseEvent &event);
|
||||||
bool is_virtual_tray();
|
bool is_virtual_tray();
|
||||||
void update_widgets();
|
void update_widgets();
|
||||||
void rebuild_filament_dropdown_items(const std::string& selected_filament_id);
|
|
||||||
void update_favorite_stars();
|
|
||||||
std::set<std::string> load_favorite_filament_ids() const;
|
|
||||||
void save_favorite_filament_ids(const std::set<std::string>& favorite_ids);
|
|
||||||
void toggle_favorite_filament(const std::string& filament_id);
|
|
||||||
|
|
||||||
void update_filament_editing(bool is_printing);
|
void update_filament_editing(bool is_printing);
|
||||||
// Orca: the variant index of the filament's per-variant options on the nozzle this tray feeds
|
// Orca: the variant index of the filament's per-variant options on the nozzle this tray feeds
|
||||||
@@ -209,7 +202,12 @@ protected:
|
|||||||
|
|
||||||
int m_pa_cali_select_id = 0;
|
int m_pa_cali_select_id = 0;
|
||||||
|
|
||||||
|
// Orca: native wxComboBox on macOS (the custom ComboBox mis-renders the filament dropdown there)
|
||||||
|
#ifdef __APPLE__
|
||||||
|
wxComboBox *m_comboBox_filament;
|
||||||
|
#else
|
||||||
ComboBox *m_comboBox_filament;
|
ComboBox *m_comboBox_filament;
|
||||||
|
#endif
|
||||||
ComboBox * m_comboBox_cali_result;
|
ComboBox * m_comboBox_cali_result;
|
||||||
TextInput* m_readonly_filament;
|
TextInput* m_readonly_filament;
|
||||||
|
|
||||||
@@ -217,20 +215,7 @@ protected:
|
|||||||
std::string filament_id;
|
std::string filament_id;
|
||||||
std::string setting_id;
|
std::string setting_id;
|
||||||
};
|
};
|
||||||
struct FilamentListItem {
|
std::map<std::string, FilamentInfos> map_filament_items;
|
||||||
FilamentInfos filament;
|
|
||||||
bool is_filament{false};
|
|
||||||
};
|
|
||||||
const FilamentInfos* selected_filament_info() const;
|
|
||||||
|
|
||||||
// Keyed by the same wxString the dropdown shows, so a row's name never has to be converted back.
|
|
||||||
std::map<wxString, FilamentInfos> map_filament_items;
|
|
||||||
std::vector<wxString> m_sorted_filament_items;
|
|
||||||
std::vector<FilamentListItem> m_filament_list_items;
|
|
||||||
ScalableBitmap m_star_filled;
|
|
||||||
ScalableBitmap m_star_empty;
|
|
||||||
// Favorites changed while the list was open; their rows are regrouped before it opens again.
|
|
||||||
bool m_favorites_regroup{false};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
wxDECLARE_EVENT(EVT_SELECTED_COLOR, wxCommandEvent);
|
wxDECLARE_EVENT(EVT_SELECTED_COLOR, wxCommandEvent);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#ifndef slic3r_GUI_SingleChoice_hpp_
|
||||||
|
#define slic3r_GUI_SingleChoice_hpp_
|
||||||
|
|
||||||
#include "GUI_Utils.hpp"
|
#include "GUI_Utils.hpp"
|
||||||
#include "Plater.hpp"
|
#include "Plater.hpp"
|
||||||
@@ -32,3 +33,5 @@ private:
|
|||||||
|
|
||||||
};
|
};
|
||||||
}} // namespace Slic3r::GUI
|
}} // namespace Slic3r::GUI
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#include <exception>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "libslic3r/Exception.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class ConfigError : public Slic3r::RuntimeError {
|
||||||
|
using Slic3r::RuntimeError::RuntimeError;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
class ConfigGUITypeError : public ConfigError {
|
||||||
|
using ConfigError::ConfigError;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -3232,16 +3232,9 @@ bool GUI_App::on_init_inner()
|
|||||||
#ifdef _MSW_DARK_MODE
|
#ifdef _MSW_DARK_MODE
|
||||||
|
|
||||||
#ifndef __WINDOWS__
|
#ifndef __WINDOWS__
|
||||||
// The label colours and the dark mode flag were set above from the saved mode. When the system theme
|
|
||||||
// differs from it, refresh them, or dialogs that map their colours stay in the old mode for this session.
|
|
||||||
const bool saved_dark_mode = dark_mode();
|
|
||||||
wxSystemAppearance app = wxSystemSettings::GetAppearance();
|
wxSystemAppearance app = wxSystemSettings::GetAppearance();
|
||||||
GUI::wxGetApp().app_config->set("dark_color_mode", app.IsDark() ? "1" : "0");
|
GUI::wxGetApp().app_config->set("dark_color_mode", app.IsDark() ? "1" : "0");
|
||||||
GUI::wxGetApp().app_config->save();
|
GUI::wxGetApp().app_config->save();
|
||||||
if (dark_mode() != saved_dark_mode) {
|
|
||||||
init_label_colours();
|
|
||||||
Update_dark_mode_flag();
|
|
||||||
}
|
|
||||||
#endif // __APPLE__
|
#endif // __APPLE__
|
||||||
|
|
||||||
|
|
||||||
@@ -4862,6 +4855,12 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
|
|||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "recreate_GUI exit";
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "recreate_GUI exit";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GUI_App::system_info()
|
||||||
|
{
|
||||||
|
//SysInfoDialog dlg;
|
||||||
|
//dlg.ShowModal();
|
||||||
|
}
|
||||||
|
|
||||||
void GUI_App::keyboard_shortcuts(ShortcutContext page, wxWindow* parent)
|
void GUI_App::keyboard_shortcuts(ShortcutContext page, wxWindow* parent)
|
||||||
{
|
{
|
||||||
KBShortcutsDialog dlg(parent != nullptr ? parent : mainframe, page);
|
KBShortcutsDialog dlg(parent != nullptr ? parent : mainframe, page);
|
||||||
|
|||||||
@@ -519,6 +519,7 @@ public:
|
|||||||
void check_printer_presets();
|
void check_printer_presets();
|
||||||
|
|
||||||
void recreate_GUI(const wxString& message);
|
void recreate_GUI(const wxString& message);
|
||||||
|
void system_info();
|
||||||
void keyboard_shortcuts(ShortcutContext page, wxWindow* parent = nullptr); // the main frame when null
|
void keyboard_shortcuts(ShortcutContext page, wxWindow* parent = nullptr); // the main frame when null
|
||||||
void troubleshoot();
|
void troubleshoot();
|
||||||
void load_project(wxWindow *parent, wxString& input_file) const;
|
void load_project(wxWindow *parent, wxString& input_file) const;
|
||||||
|
|||||||
@@ -1617,7 +1617,7 @@ void ObjectList::list_manipulation(const wxPoint& mouse_pos, bool evt_context_me
|
|||||||
int obj_idx, vol_idx;
|
int obj_idx, vol_idx;
|
||||||
get_selected_item_indexes(obj_idx, vol_idx, item);
|
get_selected_item_indexes(obj_idx, vol_idx, item);
|
||||||
if (obj_idx != -1) {
|
if (obj_idx != -1) {
|
||||||
Plater::TakeSnapshot snapshot(plater, _u8L("Shift objects to bed"));
|
Plater::TakeSnapshot(plater, _u8L("Shift objects to bed"));
|
||||||
(*m_objects)[obj_idx]->ensure_on_bed();
|
(*m_objects)[obj_idx]->ensure_on_bed();
|
||||||
cnv->reload_scene(true, true);
|
cnv->reload_scene(true, true);
|
||||||
update_info_items(obj_idx);
|
update_info_items(obj_idx);
|
||||||
@@ -2779,13 +2779,12 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
|
|||||||
GLCanvas3D* cnv = plater->canvas3D();
|
GLCanvas3D* cnv = plater->canvas3D();
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case InfoItemType::CustomSupports: {
|
case InfoItemType::CustomSupports:
|
||||||
cnv->get_gizmos_manager().reset_all_states();
|
cnv->get_gizmos_manager().reset_all_states();
|
||||||
Plater::TakeSnapshot snapshot(plater, "Remove support painting");
|
Plater::TakeSnapshot(plater, "Remove support painting");
|
||||||
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
|
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
|
||||||
mv->supported_facets.reset();
|
mv->supported_facets.reset();
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
// BBS: remove CustomSeam
|
// BBS: remove CustomSeam
|
||||||
|
|
||||||
@@ -2796,21 +2795,19 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case InfoItemType::MmSegmentation: {
|
case InfoItemType::MmSegmentation:
|
||||||
cnv->get_gizmos_manager().reset_all_states();
|
cnv->get_gizmos_manager().reset_all_states();
|
||||||
Plater::TakeSnapshot snapshot(plater, "Remove color painting");
|
Plater::TakeSnapshot(plater, "Remove color painting");
|
||||||
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
|
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
|
||||||
mv->mmu_segmentation_facets.reset();
|
mv->mmu_segmentation_facets.reset();
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case InfoItemType::FuzzySkin: {
|
case InfoItemType::FuzzySkin:
|
||||||
cnv->get_gizmos_manager().reset_all_states();
|
cnv->get_gizmos_manager().reset_all_states();
|
||||||
Plater::TakeSnapshot snapshot(plater, _u8L("Remove paint-on fuzzy skin"));
|
Plater::TakeSnapshot(plater, _u8L("Remove paint-on fuzzy skin"));
|
||||||
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
|
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
|
||||||
mv->fuzzy_skin_facets.reset();
|
mv->fuzzy_skin_facets.reset();
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
// BBS: remove Sinking
|
// BBS: remove Sinking
|
||||||
case InfoItemType::Undef : assert(false); break;
|
case InfoItemType::Undef : assert(false); break;
|
||||||
@@ -6721,7 +6718,7 @@ void ObjectList::OnEditingStarted(wxDataViewEvent &event)
|
|||||||
else if (col == colSinking) {
|
else if (col == colSinking) {
|
||||||
Plater * plater = wxGetApp().plater();
|
Plater * plater = wxGetApp().plater();
|
||||||
GLCanvas3D *cnv = plater->canvas3D();
|
GLCanvas3D *cnv = plater->canvas3D();
|
||||||
Plater::TakeSnapshot snapshot(plater, _u8L("Shift objects to bed"));
|
Plater::TakeSnapshot(plater, _u8L("Shift objects to bed"));
|
||||||
int obj_idx, vol_idx;
|
int obj_idx, vol_idx;
|
||||||
get_selected_item_indexes(obj_idx, vol_idx, item);
|
get_selected_item_indexes(obj_idx, vol_idx, item);
|
||||||
(*m_objects)[obj_idx]->ensure_on_bed();
|
(*m_objects)[obj_idx]->ensure_on_bed();
|
||||||
|
|||||||
@@ -1036,11 +1036,6 @@ bool GLGizmoPainterBase::on_mouse(const wxMouseEvent &mouse_event)
|
|||||||
Vec2d mouse_pos = mouse_coord.cast<double>();
|
Vec2d mouse_pos = mouse_coord.cast<double>();
|
||||||
|
|
||||||
if (mouse_event.Moving()) {
|
if (mouse_event.Moving()) {
|
||||||
// No button is down, yet a stroke is still open: its release went elsewhere, as a paint drag does not
|
|
||||||
// capture the mouse. End the stroke as that release would have.
|
|
||||||
if (m_button_down != Button::None)
|
|
||||||
gizmo_event(m_button_down == Button::Left ? SLAGizmoEventType::LeftUp : SLAGizmoEventType::RightUp, mouse_pos,
|
|
||||||
mouse_event.ShiftDown(), mouse_event.AltDown(), false);
|
|
||||||
gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false);
|
gizmo_event(SLAGizmoEventType::Moving, mouse_pos, mouse_event.ShiftDown(), mouse_event.AltDown(), false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1210,10 +1205,6 @@ void GLGizmoPainterBase::on_set_state()
|
|||||||
m_old_mo_id = -1;
|
m_old_mo_id = -1;
|
||||||
//m_iva.release_geometry();
|
//m_iva.release_geometry();
|
||||||
m_triangle_selectors.clear();
|
m_triangle_selectors.clear();
|
||||||
// A stroke still open goes with the selectors. Its state must not carry into the next session, where the
|
|
||||||
// first move would end it as if released and the first press would paint a line from its last position.
|
|
||||||
m_button_down = Button::None;
|
|
||||||
m_last_mouse_click = Vec2d::Zero();
|
|
||||||
|
|
||||||
//Camera& camera = wxGetApp().plater()->get_camera();
|
//Camera& camera = wxGetApp().plater()->get_camera();
|
||||||
//camera.look_at(camera.get_position(), m_previous_target, Vec3d::UnitZ());
|
//camera.look_at(camera.get_position(), m_previous_target, Vec3d::UnitZ());
|
||||||
|
|||||||
@@ -494,17 +494,7 @@ bool GLGizmoTextureDisplacement::on_init()
|
|||||||
{ctrl + _L("Mouse wheel"), m_desc["gap_area"]},
|
{ctrl + _L("Mouse wheel"), m_desc["gap_area"]},
|
||||||
toggle_wireframe_shortcut
|
toggle_wireframe_shortcut
|
||||||
};
|
};
|
||||||
|
|
||||||
// Both ways into the same input box on any slider in the panel: ImGui turns a slider into one when
|
|
||||||
// it is Ctrl+clicked or when it is given the keyboard focus, which is what the edit icon asks for.
|
|
||||||
m_desc["type_value"] = _L("Type an exact value into a slider");
|
|
||||||
m_shortcuts_sliders = {
|
|
||||||
{ctrl + _L("Left mouse button"), m_desc["type_value"]},
|
|
||||||
{_L("Edit icon at a slider's right end"), m_desc["type_value"]},
|
|
||||||
};
|
|
||||||
for (std::vector<std::pair<wxString, wxString>> *list : {&m_shortcuts_brush, &m_shortcuts_bucket_fill, &m_shortcuts_gap_fill})
|
|
||||||
list->insert(list->end(), m_shortcuts_sliders.begin(), m_shortcuts_sliders.end());
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,11 +552,6 @@ void GLGizmoTextureDisplacement::on_shutdown()
|
|||||||
m_shaded_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
|
m_shaded_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
|
||||||
m_island_drag_active = false;
|
m_island_drag_active = false;
|
||||||
m_island_move_set.clear();
|
m_island_move_set.clear();
|
||||||
// The pane closes below, taking the highlight with it, and the view must not open the next session in a
|
|
||||||
// Fast the user never picked. Set directly: nothing is to be queued against a gizmo that is closing.
|
|
||||||
if (m_island_view.switched)
|
|
||||||
m_use_shaded_preview = false;
|
|
||||||
m_island_view = IslandViewFollow{};
|
|
||||||
m_adjust_texture_mode = false;
|
m_adjust_texture_mode = false;
|
||||||
m_seam_edit_mode = false;
|
m_seam_edit_mode = false;
|
||||||
m_adjust_drag_handle = AdjustHandle::None;
|
m_adjust_drag_handle = AdjustHandle::None;
|
||||||
@@ -732,13 +717,18 @@ void GLGizmoTextureDisplacement::render_painter_gizmo()
|
|||||||
|
|
||||||
// The UV editor's island selection, shown on the model. Polled here rather than pushed: the pane
|
// The UV editor's island selection, shown on the model. Polled here rather than pushed: the pane
|
||||||
// changes its selection in its own mouse handling, and a compare of a few ints per frame is free.
|
// changes its selection in its own mouse handling, and a compare of a few ints per frame is free.
|
||||||
if (const std::vector<int> *islands = island_highlight_selection()) {
|
{
|
||||||
if (*islands != m_island_overlay_selection)
|
const TextureDisplacementLayer *al = active_layer();
|
||||||
rebuild_island_overlay(*islands);
|
const UVEditorCanvas *uv_canvas = wxGetApp().plater()->get_uv_editor_canvas();
|
||||||
render_island_overlay();
|
if (m_show_uv_editor && al != nullptr && al->projection_method == TextureProjectionMethod::LSCM &&
|
||||||
} else if (m_island_overlay_glmodel.is_initialized()) {
|
uv_canvas != nullptr && !m_uv_editor_unwrap.empty()) {
|
||||||
m_island_overlay_glmodel.reset();
|
if (uv_canvas->selected_islands() != m_island_overlay_selection)
|
||||||
m_island_overlay_selection.clear();
|
rebuild_island_overlay(uv_canvas->selected_islands());
|
||||||
|
render_island_overlay();
|
||||||
|
} else if (m_island_overlay_glmodel.is_initialized()) {
|
||||||
|
m_island_overlay_glmodel.reset();
|
||||||
|
m_island_overlay_selection.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diagnostic overlays, drawn on top of whatever preview is active (both pull toward the camera
|
// Diagnostic overlays, drawn on top of whatever preview is active (both pull toward the camera
|
||||||
@@ -1520,7 +1510,7 @@ void GLGizmoTextureDisplacement::render_shaded_preview_mesh()
|
|||||||
// is box-filtered down to 128 px for a ~48 px row, and feeding it to the shader cost the preview
|
// is box-filtered down to 128 px for a ~48 px row, and feeding it to the shader cost the preview
|
||||||
// three quarters of the height map's detail - and, since height_tex_texel is derived from it, also
|
// three quarters of the height map's detail - and, since height_tex_texel is derived from it, also
|
||||||
// flattened the shading gradient and made the parallax march skip itself at angles where it should
|
// flattened the shading gradient and made the parallax march skip itself at angles where it should
|
||||||
// run. Cached per image_data and smoothing, so no PNG is decoded per frame.
|
// run. Cached on the image_data pointer + smoothing, so no PNG is decoded per frame.
|
||||||
GLTexture *tex = get_layer_height_texture(*layer);
|
GLTexture *tex = get_layer_height_texture(*layer);
|
||||||
if (tex == nullptr || tex->get_width() <= 0 || tex->get_height() <= 0)
|
if (tex == nullptr || tex->get_width() <= 0 || tex->get_height() <= 0)
|
||||||
return;
|
return;
|
||||||
@@ -1926,16 +1916,6 @@ void GLGizmoTextureDisplacement::render_paint_overlay(GLModel &overlay)
|
|||||||
shader->stop_using();
|
shader->stop_using();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<int> *GLGizmoTextureDisplacement::island_highlight_selection()
|
|
||||||
{
|
|
||||||
const TextureDisplacementLayer *al = active_layer();
|
|
||||||
const UVEditorCanvas *uv_canvas = wxGetApp().plater()->get_uv_editor_canvas();
|
|
||||||
if (m_show_uv_editor && al != nullptr && al->projection_method == TextureProjectionMethod::LSCM &&
|
|
||||||
uv_canvas != nullptr && !m_uv_editor_unwrap.empty())
|
|
||||||
return &uv_canvas->selected_islands();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLGizmoTextureDisplacement::rebuild_island_overlay(const std::vector<int> &selection)
|
void GLGizmoTextureDisplacement::rebuild_island_overlay(const std::vector<int> &selection)
|
||||||
{
|
{
|
||||||
m_island_overlay_glmodel.reset();
|
m_island_overlay_glmodel.reset();
|
||||||
@@ -2661,37 +2641,6 @@ void GLGizmoTextureDisplacement::apply_view_mode(int mode)
|
|||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
int GLGizmoTextureDisplacement::view_mode() const
|
|
||||||
{
|
|
||||||
return m_use_shaded_preview ? 1 :
|
|
||||||
m_uv_check_mode == UVCheckMode::Checker ? 2 :
|
|
||||||
m_uv_check_mode == UVCheckMode::Distortion ? 3 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GLGizmoTextureDisplacement::IslandViewFollow::step(bool shown_now, int view, bool debug_open)
|
|
||||||
{
|
|
||||||
shown = shown_now;
|
|
||||||
if (!shown) {
|
|
||||||
// The view is back to Normal already if a debug stage took it, and Normal applied over a stage would
|
|
||||||
// queue a preview job that replaces the stage being inspected.
|
|
||||||
const bool restore = switched && view == 1;
|
|
||||||
switched = keep_normal = false;
|
|
||||||
return restore ? 0 : -1;
|
|
||||||
}
|
|
||||||
if (view != 0 || keep_normal || debug_open)
|
|
||||||
return -1;
|
|
||||||
switched = true;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLGizmoTextureDisplacement::follow_island_highlight()
|
|
||||||
{
|
|
||||||
const std::vector<int> *islands = island_highlight_selection();
|
|
||||||
const int view = m_island_view.step(islands != nullptr && !islands->empty(), view_mode(), m_debug_stage >= 0);
|
|
||||||
if (view >= 0)
|
|
||||||
apply_view_mode(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLGizmoTextureDisplacement::push_uv_pane_state()
|
void GLGizmoTextureDisplacement::push_uv_pane_state()
|
||||||
{
|
{
|
||||||
UVEditorCanvas *canvas = wxGetApp().plater()->get_uv_editor_canvas();
|
UVEditorCanvas *canvas = wxGetApp().plater()->get_uv_editor_canvas();
|
||||||
@@ -2932,8 +2881,7 @@ void GLGizmoTextureDisplacement::run_uv_command(int cmd, float value)
|
|||||||
rebuild_preview();
|
rebuild_preview();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// FrameAll/ToggleSnap are handled inside the canvas; ProjectFromView is not wired yet. SelectionChanged
|
// FrameAll/ToggleSnap are handled inside the canvas; ProjectFromView is not wired yet.
|
||||||
// only asks for this frame: follow_island_highlight() and the overlay read the selection back themselves.
|
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3610,7 +3558,6 @@ const ModelVolume* GLGizmoTextureDisplacement::texture_volume() const
|
|||||||
|
|
||||||
void GLGizmoTextureDisplacement::update_model_object()
|
void GLGizmoTextureDisplacement::update_model_object()
|
||||||
{
|
{
|
||||||
store_panel_state();
|
|
||||||
bool updated = false;
|
bool updated = false;
|
||||||
ModelObject *mo = m_c->selection_info()->model_object();
|
ModelObject *mo = m_c->selection_info()->model_object();
|
||||||
int idx = -1;
|
int idx = -1;
|
||||||
@@ -3646,24 +3593,6 @@ void GLGizmoTextureDisplacement::update_from_model_object(bool first_update)
|
|||||||
{
|
{
|
||||||
wxBusyCursor wait;
|
wxBusyCursor wait;
|
||||||
|
|
||||||
// Opening the gizmo puts the panel back where it was left: the layer and the mode come off the
|
|
||||||
// volume, because they are the user's place in this model's stack, and the dock state off the
|
|
||||||
// application config, because it is about their window. Only on open - this also runs on an
|
|
||||||
// undo/redo and on a layer switch, where overriding what the user is doing now would be wrong.
|
|
||||||
if (first_update) {
|
|
||||||
if (const ModelVolume *mv = texture_volume(); mv != nullptr) {
|
|
||||||
const TextureDisplacementOptions &opts = mv->texture_displacement_options;
|
|
||||||
m_panel_mode = std::clamp(opts.panel_mode, 0, 1);
|
|
||||||
// Only if that slot still holds a layer: the stack can have been edited by anything that
|
|
||||||
// does not go through the panel, and a selection pointing at nothing shows an empty panel.
|
|
||||||
const auto &stack = mv->texture_displacement_layers;
|
|
||||||
if (std::any_of(stack.begin(), stack.end(),
|
|
||||||
[&opts](const TextureDisplacementLayer &l) { return l.slot == opts.active_slot; }))
|
|
||||||
m_active_layer_slot = opts.active_slot;
|
|
||||||
}
|
|
||||||
m_undocked = wxGetApp().app_config->get_bool(UNDOCKED_CONFIG_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ModelObject *mo = m_c->selection_info()->model_object();
|
const ModelObject *mo = m_c->selection_info()->model_object();
|
||||||
m_triangle_selectors.clear();
|
m_triangle_selectors.clear();
|
||||||
// The base keeps the last mesh a fill tool hovered, and render_painter_gizmo() reads it as a hover that is
|
// The base keeps the last mesh a fill tool hovered, and render_painter_gizmo() reads it as a hover that is
|
||||||
@@ -3712,14 +3641,6 @@ void GLGizmoTextureDisplacement::update_from_model_object(bool first_update)
|
|||||||
rebuild_preview();
|
rebuild_preview();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoTextureDisplacement::store_panel_state()
|
|
||||||
{
|
|
||||||
if (ModelVolume *mv = texture_volume(); mv != nullptr) {
|
|
||||||
mv->texture_displacement_options.active_slot = m_active_layer_slot;
|
|
||||||
mv->texture_displacement_options.panel_mode = m_panel_mode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLGizmoTextureDisplacement::set_active_layer(int slot)
|
void GLGizmoTextureDisplacement::set_active_layer(int slot)
|
||||||
{
|
{
|
||||||
if (slot == m_active_layer_slot)
|
if (slot == m_active_layer_slot)
|
||||||
@@ -3728,7 +3649,6 @@ void GLGizmoTextureDisplacement::set_active_layer(int slot)
|
|||||||
// reflect - otherwise they would be silently lost.
|
// reflect - otherwise they would be silently lost.
|
||||||
update_model_object();
|
update_model_object();
|
||||||
m_active_layer_slot = slot;
|
m_active_layer_slot = slot;
|
||||||
store_panel_state();
|
|
||||||
update_from_model_object(false);
|
update_from_model_object(false);
|
||||||
// The on-canvas gizmo (if on) is anchored to whichever layer is active - keep it in sync
|
// The on-canvas gizmo (if on) is anchored to whichever layer is active - keep it in sync
|
||||||
// instead of leaving it pointing at the previous layer's (now stale) paint patch.
|
// instead of leaving it pointing at the previous layer's (now stale) paint patch.
|
||||||
@@ -3760,8 +3680,6 @@ void GLGizmoTextureDisplacement::ensure_panel_icons()
|
|||||||
"texture_displacement_adjust.svg", "canvas_drag.svg", "texture_displacement_move_up.svg",
|
"texture_displacement_adjust.svg", "canvas_drag.svg", "texture_displacement_move_up.svg",
|
||||||
"texture_displacement_move_down.svg", "texture_displacement_drag.svg",
|
"texture_displacement_move_down.svg", "texture_displacement_drag.svg",
|
||||||
"texture_displacement_select_all.svg", "texture_displacement_erase_all.svg",
|
"texture_displacement_select_all.svg", "texture_displacement_erase_all.svg",
|
||||||
// The "type a value instead" icon at the right end of every slider.
|
|
||||||
"edit.svg",
|
|
||||||
};
|
};
|
||||||
std::vector<std::string> paths;
|
std::vector<std::string> paths;
|
||||||
paths.reserve(names.size());
|
paths.reserve(names.size());
|
||||||
@@ -4080,8 +3998,8 @@ void GLGizmoTextureDisplacement::remove_texture_layer(int slot)
|
|||||||
if (slot >= 0 && slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS)) {
|
if (slot >= 0 && slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS)) {
|
||||||
mv->texture_displacement_facet(slot).reset();
|
mv->texture_displacement_facet(slot).reset();
|
||||||
m_thumbnails[size_t(slot)].reset();
|
m_thumbnails[size_t(slot)].reset();
|
||||||
m_thumbnail_source[size_t(slot)].reset();
|
m_thumbnail_source[size_t(slot)] = nullptr;
|
||||||
m_layer_expanded[size_t(slot)] = false;
|
m_layer_expanded[size_t(slot)] = false;
|
||||||
}
|
}
|
||||||
if (m_picker_slot == slot)
|
if (m_picker_slot == slot)
|
||||||
m_picker_slot = -1;
|
m_picker_slot = -1;
|
||||||
@@ -4255,7 +4173,7 @@ int GLGizmoTextureDisplacement::select_visible_faces(const std::array<float, 12>
|
|||||||
selector.reset();
|
selector.reset();
|
||||||
for (unsigned i : unobscured)
|
for (unsigned i : unobscured)
|
||||||
selector.set_facet(int(front_facing[i]), EnforcerBlockerType::ENFORCER);
|
selector.set_facet(int(front_facing[i]), EnforcerBlockerType::ENFORCER);
|
||||||
selector.request_update_render_data(true);
|
selector.request_update_render_data();
|
||||||
|
|
||||||
update_model_object();
|
update_model_object();
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
@@ -4278,7 +4196,7 @@ void GLGizmoTextureDisplacement::select_whole_model()
|
|||||||
const size_t facet_count = v->mesh().its.indices.size();
|
const size_t facet_count = v->mesh().its.indices.size();
|
||||||
for (size_t i = 0; i < facet_count; ++i)
|
for (size_t i = 0; i < facet_count; ++i)
|
||||||
m_triangle_selectors[idx]->set_facet(int(i), EnforcerBlockerType::ENFORCER);
|
m_triangle_selectors[idx]->set_facet(int(i), EnforcerBlockerType::ENFORCER);
|
||||||
m_triangle_selectors[idx]->request_update_render_data(true);
|
m_triangle_selectors[idx]->request_update_render_data();
|
||||||
}
|
}
|
||||||
update_model_object();
|
update_model_object();
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
@@ -5384,7 +5302,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_thumbnail(const TextureDisplace
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
const size_t slot = size_t(layer.slot);
|
const size_t slot = size_t(layer.slot);
|
||||||
if (m_thumbnails[slot] && m_thumbnail_source[slot].lock() == layer.image_data &&
|
if (m_thumbnails[slot] && m_thumbnail_source[slot] == layer.image_data.get() &&
|
||||||
m_thumbnail_smoothing[slot] == layer.smoothing)
|
m_thumbnail_smoothing[slot] == layer.smoothing)
|
||||||
return m_thumbnails[slot].get();
|
return m_thumbnails[slot].get();
|
||||||
|
|
||||||
@@ -5397,7 +5315,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_thumbnail(const TextureDisplace
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
m_thumbnails[slot] = std::move(texture);
|
m_thumbnails[slot] = std::move(texture);
|
||||||
m_thumbnail_source[slot] = layer.image_data;
|
m_thumbnail_source[slot] = layer.image_data.get();
|
||||||
m_thumbnail_smoothing[slot] = layer.smoothing;
|
m_thumbnail_smoothing[slot] = layer.smoothing;
|
||||||
return m_thumbnails[slot].get();
|
return m_thumbnails[slot].get();
|
||||||
}
|
}
|
||||||
@@ -5406,17 +5324,17 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_color_texture(const TextureDisp
|
|||||||
{
|
{
|
||||||
if (layer.empty() || !layer.color_enabled)
|
if (layer.empty() || !layer.color_enabled)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
if (m_color_tex && m_color_tex_source.lock() == layer.image_data && m_color_tex_smoothing == layer.smoothing)
|
if (m_color_tex && m_color_tex_source == layer.image_data.get() && m_color_tex_smoothing == layer.smoothing)
|
||||||
return m_color_tex.get();
|
return m_color_tex.get();
|
||||||
|
|
||||||
std::unique_ptr<GLTexture> texture = upload_color_texture(decode_height_texture(layer), HEIGHT_TEX_MAX_PX);
|
std::unique_ptr<GLTexture> texture = upload_color_texture(decode_height_texture(layer), HEIGHT_TEX_MAX_PX);
|
||||||
if (!texture) {
|
if (!texture) {
|
||||||
m_color_tex.reset();
|
m_color_tex.reset();
|
||||||
m_color_tex_source.reset();
|
m_color_tex_source = nullptr;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
m_color_tex = std::move(texture);
|
m_color_tex = std::move(texture);
|
||||||
m_color_tex_source = layer.image_data;
|
m_color_tex_source = layer.image_data.get();
|
||||||
m_color_tex_smoothing = layer.smoothing;
|
m_color_tex_smoothing = layer.smoothing;
|
||||||
return m_color_tex.get();
|
return m_color_tex.get();
|
||||||
}
|
}
|
||||||
@@ -5428,7 +5346,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_height_texture(const TextureDis
|
|||||||
|
|
||||||
// One slot, not one per layer: the preview shader only ever shades the *active* layer, so a single
|
// One slot, not one per layer: the preview shader only ever shades the *active* layer, so a single
|
||||||
// full-resolution upload is enough and the VRAM cost stays at one texture rather than eight.
|
// full-resolution upload is enough and the VRAM cost stays at one texture rather than eight.
|
||||||
if (m_height_tex && m_height_tex_source.lock() == layer.image_data && m_height_tex_smoothing == layer.smoothing)
|
if (m_height_tex && m_height_tex_source == layer.image_data.get() && m_height_tex_smoothing == layer.smoothing)
|
||||||
return m_height_tex.get();
|
return m_height_tex.get();
|
||||||
|
|
||||||
std::unique_ptr<GLTexture> texture = upload_height_thumbnail(decode_height_texture(layer), HEIGHT_TEX_MAX_PX);
|
std::unique_ptr<GLTexture> texture = upload_height_thumbnail(decode_height_texture(layer), HEIGHT_TEX_MAX_PX);
|
||||||
@@ -5436,7 +5354,7 @@ GLTexture *GLGizmoTextureDisplacement::get_layer_height_texture(const TextureDis
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
m_height_tex = std::move(texture);
|
m_height_tex = std::move(texture);
|
||||||
m_height_tex_source = layer.image_data;
|
m_height_tex_source = layer.image_data.get();
|
||||||
m_height_tex_smoothing = layer.smoothing;
|
m_height_tex_smoothing = layer.smoothing;
|
||||||
return m_height_tex.get();
|
return m_height_tex.get();
|
||||||
}
|
}
|
||||||
@@ -5877,9 +5795,6 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
GizmoImguiBegin(get_name(), flags);
|
GizmoImguiBegin(get_name(), flags);
|
||||||
ensure_panel_icons();
|
ensure_panel_icons();
|
||||||
process_uv_commands(); // clicks from the UV editor pane, run here where the GL context is current
|
process_uv_commands(); // clicks from the UV editor pane, run here where the GL context is current
|
||||||
// After the pane's commands, which can close it or change the view, and before the View row, so the row
|
|
||||||
// lights the view this frame ends up in and a click on it has the last word.
|
|
||||||
follow_island_highlight();
|
|
||||||
const float previous_body_h = m_panel_body_h;
|
const float previous_body_h = m_panel_body_h;
|
||||||
const float previous_footer_h = m_panel_footer_h;
|
const float previous_footer_h = m_panel_footer_h;
|
||||||
|
|
||||||
@@ -6004,8 +5919,8 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
}
|
}
|
||||||
return s + "...";
|
return s + "...";
|
||||||
};
|
};
|
||||||
// Label column + a slider filling the rest of the row, value printed inside the track.
|
// Label column + a slider filling the rest of the row, value printed inside the track. Ctrl+click on
|
||||||
// `right_inset` keeps a layer card's inner padding.
|
// any of these types a value in. `right_inset` keeps a layer card's inner padding.
|
||||||
const auto slider_label = [&](const wxString &label) {
|
const auto slider_label = [&](const wxString &label) {
|
||||||
const float x0 = ImGui::GetCursorPosX();
|
const float x0 = ImGui::GetCursorPosX();
|
||||||
ImGui::AlignTextToFramePadding();
|
ImGui::AlignTextToFramePadding();
|
||||||
@@ -6013,107 +5928,17 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + label_w));
|
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + label_w));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Typed values on sliders ----
|
|
||||||
// Every slider in the panel goes through typed_slider(), so all of them take a number the same
|
|
||||||
// two ways: Ctrl+click on the track, or the edit icon at the right end for the same box without
|
|
||||||
// the modifier. Both end up in ImGui's own "temp input" mode - the icon only asks ImGui to focus
|
|
||||||
// the slider, which is what turns it into an input box (see SliderScalar) - so the parsing,
|
|
||||||
// the clamping and the Enter/click-away behaviour are ImGui's, not a second implementation.
|
|
||||||
|
|
||||||
// Retires a click whose row is no longer being drawn - the panel changed mode in between - so that
|
|
||||||
// it cannot fire later when that row comes back. Before any row is drawn, so a live one still wins.
|
|
||||||
if (!m_slider_type_request.empty() && ImGui::GetFrameCount() > m_slider_type_request_frame + 1)
|
|
||||||
m_slider_type_request.clear();
|
|
||||||
|
|
||||||
// Exactly as tall as the slider it sits beside, so it lines up with the track without the row
|
|
||||||
// having to be re-centred around it; icon_button() pads the glyph itself down to about 0.7 of this.
|
|
||||||
const float edit_icon_w = frame_h;
|
|
||||||
// What the slider's own width has to give up for the icon beside it.
|
|
||||||
const float edit_icon_room = edit_icon_w + gap_s;
|
|
||||||
|
|
||||||
// One end of a range, through the slider's own format, so the bounds in the tooltip read exactly
|
|
||||||
// like the value in the track. A parenthesised note in the format - "(auto)" - is dropped: it
|
|
||||||
// describes the current state, not the bound.
|
|
||||||
const auto fmt_bound = [](const char *format, auto v) {
|
|
||||||
char buf[64];
|
|
||||||
std::snprintf(buf, sizeof(buf), format, v);
|
|
||||||
std::string out(buf);
|
|
||||||
const size_t paren = out.find('(');
|
|
||||||
if (paren != std::string::npos)
|
|
||||||
out.erase(paren);
|
|
||||||
while (!out.empty() && out.back() == ' ')
|
|
||||||
out.pop_back();
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
const auto range_text = [](const std::string &lo, const std::string &hi) {
|
|
||||||
return Slic3r::format(_u8L("Range: %1% to %2%"), lo, hi);
|
|
||||||
};
|
|
||||||
const auto edit_icon_tip = [&](const std::string &range) {
|
|
||||||
return _u8L("Type a value instead of dragging. Ctrl+click on the slider does the same.") + "\n\n" + range;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Draws `slider` at `item_w` (SetNextItemWidth's convention: negative measures back from the right
|
|
||||||
// edge, and the caller has already left edit_icon_room in it), then the edit icon. `tip` and the
|
|
||||||
// range go on both. Returns whatever the slider returned.
|
|
||||||
const auto typed_row = [&](const char *id, float item_w, const wxString &tip, const std::string &range,
|
|
||||||
const std::function<bool()> &slider) -> bool {
|
|
||||||
// Deferred from the click a frame ago: SetKeyboardFocusHere() is a request for the *next*
|
|
||||||
// frame, so it has to be issued before the slider it names, not from the icon beside it.
|
|
||||||
if (m_slider_type_request == id) {
|
|
||||||
ImGui::SetKeyboardFocusHere();
|
|
||||||
m_slider_type_request.clear();
|
|
||||||
m_parent.set_as_dirty(); // the focus only lands on a frame that actually gets drawn
|
|
||||||
}
|
|
||||||
ImGui::SetNextItemWidth(item_w);
|
|
||||||
const bool changed = slider();
|
|
||||||
// AllowWhenDisabled: several of these rows are drawn inside disabled_begin(), and a greyed
|
|
||||||
// slider is exactly when the user needs to be told what it is and why it is not moving.
|
|
||||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
|
|
||||||
wxString full = tip;
|
|
||||||
if (!full.IsEmpty())
|
|
||||||
full += "\n\n";
|
|
||||||
m_imgui->tooltip(full + from_u8(range), wrap_w);
|
|
||||||
}
|
|
||||||
// SameLine() puts the cursor back at the slider's own top (ItemSize records the Y the item was
|
|
||||||
// drawn at), so an icon of the same height lands on the track wherever the row placed it.
|
|
||||||
ImGui::SameLine(0.f, gap_s);
|
|
||||||
if (icon_button(int(ImGui::GetID(id)), "edit.svg", edit_icon_w, _L("Edit"), from_u8(edit_icon_tip(range)))) {
|
|
||||||
m_slider_type_request = id;
|
|
||||||
m_slider_type_request_frame = ImGui::GetFrameCount();
|
|
||||||
m_parent.set_as_dirty(); // the next frame is where the request above is turned into focus
|
|
||||||
}
|
|
||||||
return changed;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The two forms every slider in the panel is one of. They derive the range from the bounds they are
|
|
||||||
// already given, so no row states its bounds or its format twice and the tooltip cannot drift from
|
|
||||||
// the track. `item_w` is as typed_row() takes it.
|
|
||||||
const auto typed_slider = [&](const char *id, float item_w, float *v, float v_min, float v_max, const char *format,
|
|
||||||
bool log, const wxString &tip) -> bool {
|
|
||||||
return typed_row(id, item_w, tip, range_text(fmt_bound(format, v_min), fmt_bound(format, v_max)), [&] {
|
|
||||||
return ImGui::SliderFloat(id, v, v_min, v_max, format,
|
|
||||||
ImGuiSliderFlags_AlwaysClamp | (log ? ImGuiSliderFlags_Logarithmic : 0));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const auto typed_slider_int = [&](const char *id, float item_w, int *v, int v_min, int v_max, const char *format,
|
|
||||||
bool log, const wxString &tip) -> bool {
|
|
||||||
return typed_row(id, item_w, tip, range_text(fmt_bound(format, v_min), fmt_bound(format, v_max)), [&] {
|
|
||||||
return ImGui::SliderInt(id, v, v_min, v_max, format,
|
|
||||||
ImGuiSliderFlags_AlwaysClamp | (log ? ImGuiSliderFlags_Logarithmic : 0));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Label column + the slider filling the rest of the row.
|
|
||||||
const auto float_row = [&](const char *id, const wxString &label, float *v, float v_min, float v_max, const char *format,
|
const auto float_row = [&](const char *id, const wxString &label, float *v, float v_min, float v_max, const char *format,
|
||||||
bool log, float right_inset, const wxString &tip) -> bool {
|
bool log, float right_inset) -> bool {
|
||||||
slider_label(label);
|
slider_label(label);
|
||||||
return typed_slider(id, -(std::max(right_inset, 1.f) + edit_icon_room), v, v_min, v_max, format, log, tip);
|
ImGui::SetNextItemWidth(-std::max(right_inset, 1.f));
|
||||||
|
return ImGui::SliderFloat(id, v, v_min, v_max, format, ImGuiSliderFlags_AlwaysClamp | (log ? ImGuiSliderFlags_Logarithmic : 0));
|
||||||
};
|
};
|
||||||
const auto int_row = [&](const char *id, const wxString &label, int *v, int v_min, int v_max, const char *format,
|
const auto int_row = [&](const char *id, const wxString &label, int *v, int v_min, int v_max, const char *format,
|
||||||
float right_inset, const wxString &tip) -> bool {
|
float right_inset) -> bool {
|
||||||
slider_label(label);
|
slider_label(label);
|
||||||
return typed_slider_int(id, -(std::max(right_inset, 1.f) + edit_icon_room), v, v_min, v_max, format, false, tip);
|
ImGui::SetNextItemWidth(-std::max(right_inset, 1.f));
|
||||||
|
return ImGui::SliderInt(id, v, v_min, v_max, format, ImGuiSliderFlags_AlwaysClamp);
|
||||||
};
|
};
|
||||||
const auto layer_name = [](const TextureDisplacementLayer &l) {
|
const auto layer_name = [](const TextureDisplacementLayer &l) {
|
||||||
return l.name.empty() ? Slic3r::format(_u8L("Layer %1%"), l.slot + 1) : l.name;
|
return l.name.empty() ? Slic3r::format(_u8L("Layer %1%"), l.slot + 1) : l.name;
|
||||||
@@ -6161,7 +5986,6 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
"not what you wanted and you know why."));
|
"not what you wanted and you know why."));
|
||||||
if (clicked && !on) {
|
if (clicked && !on) {
|
||||||
m_panel_mode = m;
|
m_panel_mode = m;
|
||||||
store_panel_state();
|
|
||||||
if (!pro_mode()) {
|
if (!pro_mode()) {
|
||||||
// Leaving the subdivision preview open would strand a wireframe whose controls just
|
// Leaving the subdivision preview open would strand a wireframe whose controls just
|
||||||
// disappeared, so close it as part of the switch.
|
// disappeared, so close it as part of the switch.
|
||||||
@@ -6184,7 +6008,6 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
_L("Detach this panel so it can be dragged anywhere over the 3D view, or dock it back beside "
|
_L("Detach this panel so it can be dragged anywhere over the 3D view, or dock it back beside "
|
||||||
"the toolbar.")))
|
"the toolbar.")))
|
||||||
m_undocked = !m_undocked;
|
m_undocked = !m_undocked;
|
||||||
wxGetApp().app_config->set_bool(UNDOCKED_CONFIG_KEY, m_undocked);
|
|
||||||
}
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
@@ -6326,7 +6149,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
if (v->is_model_part()) {
|
if (v->is_model_part()) {
|
||||||
++idx;
|
++idx;
|
||||||
m_triangle_selectors[idx]->reset();
|
m_triangle_selectors[idx]->reset();
|
||||||
m_triangle_selectors[idx]->request_update_render_data(true);
|
m_triangle_selectors[idx]->request_update_render_data();
|
||||||
}
|
}
|
||||||
update_model_object();
|
update_model_object();
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
@@ -6337,10 +6160,11 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
const auto centre_on_row = [&](float h) { ImGui::SetCursorPosY(row_y + std::round((icon_md - h) * 0.5f)); };
|
const auto centre_on_row = [&](float h) { ImGui::SetCursorPosY(row_y + std::round((icon_md - h) * 0.5f)); };
|
||||||
vsep(icon_md);
|
vsep(icon_md);
|
||||||
if (is_brush_mode) {
|
if (is_brush_mode) {
|
||||||
|
ImGui::SetNextItemWidth(std::max(1.f, row_end - ImGui::GetCursorPosX() - (3.f * gap_s + 1.f + 2.f * icon_sm)));
|
||||||
centre_on_row(frame_h);
|
centre_on_row(frame_h);
|
||||||
typed_slider("##cursor_radius",
|
ImGui::SliderFloat("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f mm",
|
||||||
std::max(1.f, row_end - ImGui::GetCursorPosX() - (3.f * gap_s + 1.f + 2.f * icon_sm) - edit_icon_room),
|
ImGuiSliderFlags_AlwaysClamp);
|
||||||
&m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f mm", false, m_desc.at("cursor_size"));
|
hover_tip(m_desc.at("cursor_size"));
|
||||||
vsep(icon_sm);
|
vsep(icon_sm);
|
||||||
const bool is_circle = m_cursor_type == TriangleSelector::CursorType::CIRCLE;
|
const bool is_circle = m_cursor_type == TriangleSelector::CursorType::CIRCLE;
|
||||||
centre_on_row(icon_sm);
|
centre_on_row(icon_sm);
|
||||||
@@ -6353,10 +6177,11 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
_L("Sphere - paints only within a ball around the point under the cursor")))
|
_L("Sphere - paints only within a ball around the point under the cursor")))
|
||||||
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
||||||
} else if (is_area_mode) {
|
} else if (is_area_mode) {
|
||||||
|
ImGui::SetNextItemWidth(std::max(1.f, row_end - ImGui::GetCursorPosX()));
|
||||||
centre_on_row(frame_h);
|
centre_on_row(frame_h);
|
||||||
typed_slider("##smart_fill_angle", std::max(1.f, row_end - ImGui::GetCursorPosX() - edit_icon_room),
|
ImGui::SliderFloat("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, "%.0f°",
|
||||||
&m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, "%.0f°", false,
|
ImGuiSliderFlags_AlwaysClamp);
|
||||||
_L("Angle threshold - the fill stops at edges sharper than this"));
|
hover_tip(_u8L("Angle threshold - the fill stops at edges sharper than this"));
|
||||||
} else {
|
} else {
|
||||||
centre_on_row(frame_h);
|
centre_on_row(frame_h);
|
||||||
ImGui::AlignTextToFramePadding();
|
ImGui::AlignTextToFramePadding();
|
||||||
@@ -6368,9 +6193,10 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
// ---- View: Normal / Fast / Checker / Distortion as one group, Wireframe on its own ----
|
// ---- View: Normal / Fast / Checker / Distortion as one group, Wireframe on its own ----
|
||||||
// The underlying state stays m_use_shaded_preview + m_uv_check_mode.
|
// The underlying state stays m_use_shaded_preview + m_uv_check_mode.
|
||||||
{
|
{
|
||||||
const int cur_mode = view_mode();
|
const int cur_mode = m_use_shaded_preview ? 1 :
|
||||||
|
m_uv_check_mode == UVCheckMode::Checker ? 2 :
|
||||||
|
m_uv_check_mode == UVCheckMode::Distortion ? 3 : 0;
|
||||||
int new_mode = cur_mode;
|
int new_mode = cur_mode;
|
||||||
int picked = -1; // the button clicked, the active one included
|
|
||||||
bool wf_toggle = false;
|
bool wf_toggle = false;
|
||||||
bool open_uv_editor = false;
|
bool open_uv_editor = false;
|
||||||
// Checker and Distortion both draw *the unwrap* - the first the texture grid laid over it, the second
|
// Checker and Distortion both draw *the unwrap* - the first the texture grid laid over it, the second
|
||||||
@@ -6393,19 +6219,19 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + m_imgui->scaled(2.6f)));
|
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + m_imgui->scaled(2.6f)));
|
||||||
if (icon_toggle(701, "texture_displacement_real_preview.svg", cur_mode == 0, icon_md, _L("Normal"),
|
if (icon_toggle(701, "texture_displacement_real_preview.svg", cur_mode == 0, icon_md, _L("Normal"),
|
||||||
_L("Normal - the real geometry, exactly what Bake will produce. Slower to update")))
|
_L("Normal - the real geometry, exactly what Bake will produce. Slower to update")))
|
||||||
new_mode = picked = 0;
|
new_mode = 0;
|
||||||
ImGui::SameLine(0.f, gap_s);
|
ImGui::SameLine(0.f, gap_s);
|
||||||
if (icon_toggle(702, "texture_displacement_fast_preview.svg", cur_mode == 1, icon_md, _L("Fast"),
|
if (icon_toggle(702, "texture_displacement_fast_preview.svg", cur_mode == 1, icon_md, _L("Fast"),
|
||||||
_L("Fast - the relief is only shaded on, not built, and only for the layer you are "
|
_L("Fast - the relief is only shaded on, not built, and only for the layer you are "
|
||||||
"editing. Updates instantly while you paint")))
|
"editing. Updates instantly while you paint")))
|
||||||
new_mode = picked = 1;
|
new_mode = 1;
|
||||||
ImGui::SameLine(0.f, gap_s);
|
ImGui::SameLine(0.f, gap_s);
|
||||||
if (icon_toggle(703, "texture_displacement_checker.svg", cur_mode == 2, icon_md, _L("Checker"),
|
if (icon_toggle(703, "texture_displacement_checker.svg", cur_mode == 2, icon_md, _L("Checker"),
|
||||||
_L("Checker - a test grid instead of the texture. Where the squares stay square the "
|
_L("Checker - a test grid instead of the texture. Where the squares stay square the "
|
||||||
"texture is undistorted; where they stretch, it will too. Opens the UV editor if "
|
"texture is undistorted; where they stretch, it will too. Opens the UV editor if "
|
||||||
"it is closed"),
|
"it is closed"),
|
||||||
uv_view_na)) {
|
uv_view_na)) {
|
||||||
new_mode = picked = 2;
|
new_mode = 2;
|
||||||
open_uv_editor = true;
|
open_uv_editor = true;
|
||||||
}
|
}
|
||||||
ImGui::SameLine(0.f, gap_s);
|
ImGui::SameLine(0.f, gap_s);
|
||||||
@@ -6413,7 +6239,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
_L("Distortion - blue-to-red stretch heatmap over the unwrap. Opens the UV editor if "
|
_L("Distortion - blue-to-red stretch heatmap over the unwrap. Opens the UV editor if "
|
||||||
"it is closed"),
|
"it is closed"),
|
||||||
uv_view_na)) {
|
uv_view_na)) {
|
||||||
new_mode = picked = 3;
|
new_mode = 3;
|
||||||
open_uv_editor = true;
|
open_uv_editor = true;
|
||||||
}
|
}
|
||||||
vsep(icon_md);
|
vsep(icon_md);
|
||||||
@@ -6437,8 +6263,6 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
if (new_mode == cur_mode)
|
if (new_mode == cur_mode)
|
||||||
update_uv_editor(); // otherwise apply_view_mode() below does it
|
update_uv_editor(); // otherwise apply_view_mode() below does it
|
||||||
}
|
}
|
||||||
if (picked >= 0)
|
|
||||||
m_island_view.picked(picked);
|
|
||||||
if (new_mode != cur_mode)
|
if (new_mode != cur_mode)
|
||||||
apply_view_mode(new_mode);
|
apply_view_mode(new_mode);
|
||||||
if (wf_toggle) {
|
if (wf_toggle) {
|
||||||
@@ -6627,16 +6451,16 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
|
|
||||||
// The three controls nearly every layer needs; everything else waits behind "More settings".
|
// The three controls nearly every layer needs; everything else waits behind "More settings".
|
||||||
// Depth and tile size are logarithmic, so both ends of their wide ranges stay usable.
|
// Depth and tile size are logarithmic, so both ends of their wide ranges stay usable.
|
||||||
m_preview_params_dirty |= float_row("##depth", _L("Depth"), &layer.depth_mm, 0.01f, 10.f, "%.3f mm", true, card_pad,
|
m_preview_params_dirty |= float_row("##depth", _L("Depth"), &layer.depth_mm, 0.01f, 10.f, "%.3f mm", true, card_pad);
|
||||||
_L("Height of the relief: how far white in the texture lifts the surface, in "
|
hover_tip(_u8L("Height of the relief: how far white in the texture lifts the surface, in "
|
||||||
"millimetres. Black does not move it at all, unless Midlevel says otherwise."));
|
"millimetres. Black does not move it at all, unless Midlevel says otherwise."));
|
||||||
m_preview_params_dirty |= float_row("##tile_size", _L("Tile size"), &layer.tiling_scale, 0.2f, 200.f, "%.2f mm", true, card_pad,
|
m_preview_params_dirty |= float_row("##tile_size", _L("Tile size"), &layer.tiling_scale, 0.2f, 200.f, "%.2f mm", true, card_pad);
|
||||||
_L("How wide one copy of the texture is on the model. Smaller repeats the pattern "
|
hover_tip(_u8L("How wide one copy of the texture is on the model. Smaller repeats the pattern "
|
||||||
"more often and makes its detail finer; with Tile off, this is the size of the "
|
"more often and makes its detail finer; with Tile off, this is the size of the "
|
||||||
"single copy."));
|
"single copy."));
|
||||||
m_preview_params_dirty |= float_row("##rotation", _L("Rotation"), &layer.rotation_deg, 0.f, 360.f, "%.0f°", false, card_pad,
|
m_preview_params_dirty |= float_row("##rotation", _L("Rotation"), &layer.rotation_deg, 0.f, 360.f, "%.0f°", false, card_pad);
|
||||||
_L("Turns the texture on the surface, in degrees - for lining a pattern up with an "
|
hover_tip(_u8L("Turns the texture on the surface, in degrees - for lining a pattern up with an "
|
||||||
"edge of the model."));
|
"edge of the model."));
|
||||||
|
|
||||||
if (m_layer_expanded[size_t(layer.slot)]) {
|
if (m_layer_expanded[size_t(layer.slot)]) {
|
||||||
{
|
{
|
||||||
@@ -6651,22 +6475,22 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
|
|
||||||
// Midlevel: the height that means "don't move". At 0 the surface only ever bulges outwards;
|
// Midlevel: the height that means "don't move". At 0 the surface only ever bulges outwards;
|
||||||
// at 0.5 mid-grey is neutral and darker texels cut inwards.
|
// at 0.5 mid-grey is neutral and darker texels cut inwards.
|
||||||
m_preview_params_dirty |= float_row("##midlevel", _L("Midlevel"), &layer.midlevel, 0.f, 10.f, "%.2f", false, card_pad,
|
m_preview_params_dirty |= float_row("##midlevel", _L("Midlevel"), &layer.midlevel, 0.f, 10.f, "%.2f", false, card_pad);
|
||||||
_L("Which gray stays where the surface already is. At 0 the texture only pushes "
|
hover_tip(_u8L("Which gray stays where the surface already is. At 0 the texture only pushes "
|
||||||
"outwards; at 0.5 mid-gray stays put, so darker grays cut in and lighter ones "
|
"outwards; at 0.5 mid-gray stays put, so darker grays cut in and lighter ones "
|
||||||
"still push out - one image both embosses and engraves.\n\n"
|
"still push out - one image both embosses and engraves.\n\n"
|
||||||
"What cuts in has to fit: inside a sharp corner or through a thin wall, a deep "
|
"What cuts in has to fit: inside a sharp corner or through a thin wall, a deep "
|
||||||
"cut can pass through the other side."));
|
"cut can pass through the other side."));
|
||||||
if (layer.midlevel > 0.f && layer.depth_mm > 1.f) {
|
if (layer.midlevel > 0.f && layer.depth_mm > 1.f) {
|
||||||
ImGui::PushTextWrapPos(content_rx - card_pad);
|
ImGui::PushTextWrapPos(content_rx - card_pad);
|
||||||
m_imgui->warning_text(_L("Deep inward displacement may self-intersect."));
|
m_imgui->warning_text(_L("Deep inward displacement may self-intersect."));
|
||||||
ImGui::PopTextWrapPos();
|
ImGui::PopTextWrapPos();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_preview_params_dirty |= float_row("##smoothing", _L("Smoothing"), &layer.smoothing, 0.f, 1.f, "%.2f", false, card_pad,
|
m_preview_params_dirty |= float_row("##smoothing", _L("Smoothing"), &layer.smoothing, 0.f, 1.f, "%.2f", false, card_pad);
|
||||||
_L("Blurs the image before it is used, which rounds off hard steps and removes "
|
hover_tip(_u8L("Blurs the image before it is used, which rounds off hard steps and removes "
|
||||||
"speckle from a noisy photo. Raise it if the relief looks harsh or grainy; it "
|
"speckle from a noisy photo. Raise it if the relief looks harsh or grainy; it "
|
||||||
"costs fine detail."));
|
"costs fine detail."));
|
||||||
|
|
||||||
// Edge fade: the relief flattens toward the boundary of the painted area.
|
// Edge fade: the relief flattens toward the boundary of the painted area.
|
||||||
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Edge fade") + "##edge_smoothing", layer.edge_smoothing);
|
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Edge fade") + "##edge_smoothing", layer.edge_smoothing);
|
||||||
@@ -6674,11 +6498,12 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
"blends into the bare surface instead of stopping at a step."));
|
"blends into the bare surface instead of stopping at a step."));
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
m_imgui->disabled_begin(!layer.edge_smoothing);
|
m_imgui->disabled_begin(!layer.edge_smoothing);
|
||||||
m_preview_params_dirty |= typed_slider("##edge_amount", -(card_pad + edit_icon_room),
|
ImGui::SetNextItemWidth(-card_pad);
|
||||||
&layer.edge_smoothing_amount, 0.02f, 1.f, "%.2f", false,
|
m_preview_params_dirty |= ImGui::SliderFloat("##edge_amount", &layer.edge_smoothing_amount, 0.02f, 1.f, "%.2f",
|
||||||
_L("How far in the fade reaches, as a share of the painted area. Small "
|
ImGuiSliderFlags_AlwaysClamp);
|
||||||
"values soften a narrow band at the edge; 1 flattens almost all of it."));
|
|
||||||
m_imgui->disabled_end();
|
m_imgui->disabled_end();
|
||||||
|
hover_tip(_u8L("How far in the fade reaches, as a share of the painted area. Small values "
|
||||||
|
"soften a narrow band at the edge; 1 flattens almost all of it."));
|
||||||
|
|
||||||
// Invert and Colours share a row.
|
// Invert and Colours share a row.
|
||||||
{
|
{
|
||||||
@@ -6723,19 +6548,19 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
// Every mix can become a filament slot, and there are only so many beside the
|
// Every mix can become a filament slot, and there are only so many beside the
|
||||||
// physical filaments.
|
// physical filaments.
|
||||||
const int max_mixes = std::max(1, int(EnforcerBlockerType::ExtruderMax) - int(m_palette_filaments.size()));
|
const int max_mixes = std::max(1, int(EnforcerBlockerType::ExtruderMax) - int(m_palette_filaments.size()));
|
||||||
if (int_row("##color_mix_count", _L("Mixed colors"), &opts.color_mix_count, 1, max_mixes, "%d", card_pad,
|
if (int_row("##color_mix_count", _L("Mixed colors"), &opts.color_mix_count, 1, max_mixes, "%d", card_pad))
|
||||||
_L("The most mixed filaments a bake may add. They are picked from the "
|
|
||||||
"texture's colors, and only the ones the bake actually uses are created.")))
|
|
||||||
m_preview_params_dirty = true;
|
m_preview_params_dirty = true;
|
||||||
|
hover_tip(_u8L("The most mixed filaments a bake may add. They are picked from the "
|
||||||
|
"texture's colors, and only the ones the bake actually uses are created."));
|
||||||
// After the slider, so a change shows in the same frame.
|
// After the slider, so a change shows in the same frame.
|
||||||
ImGui::TextDisabled("%s", Slic3r::format(_u8L("%1% printable colors from %2% filaments"),
|
ImGui::TextDisabled("%s", Slic3r::format(_u8L("%1% printable colors from %2% filaments"),
|
||||||
int(cached_palette().size()), int(m_palette_filaments.size())).c_str());
|
int(cached_palette().size()), int(m_palette_filaments.size())).c_str());
|
||||||
}
|
}
|
||||||
if (int_row("##color_despeckle", _L("Denoise"), &opts.color_despeckle, 0, 6, "%d", card_pad,
|
if (int_row("##color_despeckle", _L("Denoise"), &opts.color_despeckle, 0, 6, "%d", card_pad))
|
||||||
_L("Cleans up single stray triangles of the wrong color, which detail finer "
|
|
||||||
"than the mesh leaves behind. Raise it if the result looks speckled, "
|
|
||||||
"lower it if small features are being swallowed.")))
|
|
||||||
m_preview_params_dirty = true;
|
m_preview_params_dirty = true;
|
||||||
|
hover_tip(_u8L("Cleans up single stray triangles of the wrong color, which detail finer "
|
||||||
|
"than the mesh leaves behind. Raise it if the result looks speckled, "
|
||||||
|
"lower it if small features are being swallowed."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6840,10 +6665,10 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
"gets the texture, and its border becomes the edge of the projection."));
|
"gets the texture, and its border becomes the edge of the projection."));
|
||||||
|
|
||||||
if (projector_open) {
|
if (projector_open) {
|
||||||
if (int_row("##projector_opacity", _L("Opacity"), &m_projector_opacity, 20, 255, "%d", card_pad,
|
if (int_row("##projector_opacity", _L("Opacity"), &m_projector_opacity, 20, 255, "%d", card_pad))
|
||||||
_L("How solid the frame window looks while you place it. Lower to see the "
|
|
||||||
"model through it; it does not affect the result.")))
|
|
||||||
m_projector_frame->set_opacity(m_projector_opacity);
|
m_projector_frame->set_opacity(m_projector_opacity);
|
||||||
|
hover_tip(_u8L("How solid the frame window looks while you place it. Lower to see the "
|
||||||
|
"model through it; it does not affect the result."));
|
||||||
|
|
||||||
if (m_imgui->button(_u8L("Apply projection frame"))) {
|
if (m_imgui->button(_u8L("Apply projection frame"))) {
|
||||||
const int painted_count = apply_projection_frame();
|
const int painted_count = apply_projection_frame();
|
||||||
@@ -7144,57 +6969,57 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
// pattern can sample four points that all happen to land at similar heights, report no error,
|
// pattern can sample four points that all happen to land at similar heights, report no error,
|
||||||
// and stall before refinement ever starts.
|
// and stall before refinement ever starts.
|
||||||
if (float_row("##subdiv_target", m_subdivide_feature ? _L("Max edge") : _L("Target edge"), &m_subdivide_target_mm,
|
if (float_row("##subdiv_target", m_subdivide_feature ? _L("Max edge") : _L("Target edge"), &m_subdivide_target_mm,
|
||||||
0.001f, 20.f, "%.3f mm", true, 0.f,
|
0.001f, 20.f, "%.3f mm", true, 0.f))
|
||||||
m_subdivide_feature ?
|
|
||||||
_L("No triangle in the painted area stays larger than this, even where the texture "
|
|
||||||
"is flat. Keep it near the size of the smallest feature you want to come out.") :
|
|
||||||
_L("Triangles in the painted area are split until none is larger than this. Smaller "
|
|
||||||
"means finer detail and, fast, a lot more triangles.")))
|
|
||||||
preview_live();
|
preview_live();
|
||||||
|
hover_tip(m_subdivide_feature ?
|
||||||
|
_u8L("No triangle in the painted area stays larger than this, even where the texture "
|
||||||
|
"is flat. Keep it near the size of the smallest feature you want to come out.") :
|
||||||
|
_u8L("Triangles in the painted area are split until none is larger than this. Smaller "
|
||||||
|
"means finer detail and, fast, a lot more triangles."));
|
||||||
|
|
||||||
if (m_subdivide_feature) {
|
if (m_subdivide_feature) {
|
||||||
if (float_row("##subdiv_detail", _L("Detail"), &m_subdivide_detail_mm, 0.001f, 1.f, "%.3f mm", true, 0.f,
|
if (float_row("##subdiv_detail", _L("Detail"), &m_subdivide_detail_mm, 0.001f, 1.f, "%.3f mm", true, 0.f))
|
||||||
_L("How far the mesh may sit from the shape the texture describes, in millimetres. "
|
|
||||||
"Smaller follows fine detail and costs triangles; larger only chases the big "
|
|
||||||
"features.")))
|
|
||||||
preview_live();
|
preview_live();
|
||||||
if (float_row("##subdiv_min", _L("Min edge"), &m_subdivide_min_edge_mm, 0.001f, 20.f, "%.3f mm", true, 0.f,
|
hover_tip(_u8L("How far the mesh may sit from the shape the texture describes, in millimetres. "
|
||||||
_L("A floor on triangle size, so a sharp step in the texture cannot be chased "
|
"Smaller follows fine detail and costs triangles; larger only chases the big "
|
||||||
"forever. Lower it for finer relief, raise it if the count runs away at hard "
|
"features."));
|
||||||
"edges.")))
|
if (float_row("##subdiv_min", _L("Min edge"), &m_subdivide_min_edge_mm, 0.001f, 20.f, "%.3f mm", true, 0.f))
|
||||||
preview_live();
|
preview_live();
|
||||||
|
hover_tip(_u8L("A floor on triangle size, so a sharp step in the texture cannot be chased "
|
||||||
|
"forever. Lower it for finer relief, raise it if the count runs away at hard "
|
||||||
|
"edges."));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applies in both adaptive sub-modes: it is about the step the bake puts at the paint's edge, which
|
// Applies in both adaptive sub-modes: it is about the step the bake puts at the paint's edge, which
|
||||||
// exists whether or not "Follow texture detail" is on. 0 turns the band off.
|
// exists whether or not "Follow texture detail" is on. 0 turns the band off.
|
||||||
if (float_row("##subdiv_border", _L("Edge detail"), &m_subdivide_border_mm, 0.f, 5.f, "%.3f mm", false, 0.f,
|
if (float_row("##subdiv_border", _L("Edge detail"), &m_subdivide_border_mm, 0.f, 5.f, "%.3f mm", false, 0.f))
|
||||||
_L("Triangle size along the outline of the painted area, where the relief drops back to the "
|
|
||||||
"bare surface. Lower it if that rim looks jagged; it only costs triangles along the "
|
|
||||||
"outline. 0 turns it off.")))
|
|
||||||
preview_live();
|
preview_live();
|
||||||
|
hover_tip(_u8L("Triangle size along the outline of the painted area, where the relief drops back to the "
|
||||||
|
"bare surface. Lower it if that rim looks jagged; it only costs triangles along the "
|
||||||
|
"outline. 0 turns it off."));
|
||||||
|
|
||||||
// Only worth showing when a layer is actually colouring: with no colour there is no boundary for it
|
// Only worth showing when a layer is actually colouring: with no colour there is no boundary for it
|
||||||
// to refine and the control would do nothing whatever it is set to.
|
// to refine and the control would do nothing whatever it is set to.
|
||||||
if (mv != nullptr && any_layer_colors(*mv)) {
|
if (mv != nullptr && any_layer_colors(*mv)) {
|
||||||
if (float_row("##subdiv_color", _L("Color detail"), &m_subdivide_color_mm, 0.f, 5.f, "%.3f mm", false, 0.f,
|
if (float_row("##subdiv_color", _L("Color detail"), &m_subdivide_color_mm, 0.f, 5.f, "%.3f mm", false, 0.f))
|
||||||
_L("Triangle size where two colors meet. Each triangle prints in one filament, so a "
|
|
||||||
"color edge can only be as sharp as the triangles along it - and nothing else "
|
|
||||||
"refines there, since the surface is flat across a change of color. 0 turns it off.")))
|
|
||||||
preview_live();
|
preview_live();
|
||||||
|
hover_tip(_u8L("Triangle size where two colors meet. Each triangle prints in one filament, so a "
|
||||||
|
"color edge can only be as sharp as the triangles along it - and nothing else "
|
||||||
|
"refines there, since the surface is flat across a change of color. 0 turns it off."));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::TextDisabled("%s", _u8L("Triangle budget: set with Triangles, below.").c_str());
|
ImGui::TextDisabled("%s", _u8L("Triangle budget: set with Triangles, below.").c_str());
|
||||||
if (m_subdivide_editing && m_subdivide_preview_tris > 0)
|
if (m_subdivide_editing && m_subdivide_preview_tris > 0)
|
||||||
m_imgui->text(Slic3r::format(_u8L("Preview: %1% triangles"), m_subdivide_preview_tris));
|
m_imgui->text(Slic3r::format(_u8L("Preview: %1% triangles"), m_subdivide_preview_tris));
|
||||||
} else {
|
} else {
|
||||||
if (int_row("##subdiv_steps", _L("Steps"), &m_subdivide_count, 0, 5, "%d", 0.f,
|
if (int_row("##subdiv_steps", _L("Steps"), &m_subdivide_count, 0, 5, "%d", 0.f)) {
|
||||||
_L("Splits every triangle of the model into four, this many times over. Each step "
|
|
||||||
"quadruples the count - 3 turns 100 k triangles into 6.4 M - so prefer refining "
|
|
||||||
"only the painted area unless you need the whole model finer."))) {
|
|
||||||
if (m_subdivide_editing)
|
if (m_subdivide_editing)
|
||||||
rebuild_subdivide_preview(); // a count of 0 clears the preview, it doesn't compute one
|
rebuild_subdivide_preview(); // a count of 0 clears the preview, it doesn't compute one
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
}
|
}
|
||||||
|
hover_tip(_u8L("Splits every triangle of the model into four, this many times over. Each step "
|
||||||
|
"quadruples the count - 3 turns 100 k triangles into 6.4 M - so prefer refining "
|
||||||
|
"only the painted area unless you need the whole model finer."));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -7256,9 +7081,9 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
}
|
}
|
||||||
m_remesh_target_edge_mm = cnt > 0 ? std::clamp(float(sum / double(cnt)), 0.1f, 20.f) : 1.f;
|
m_remesh_target_edge_mm = cnt > 0 ? std::clamp(float(sum / double(cnt)), 0.1f, 20.f) : 1.f;
|
||||||
}
|
}
|
||||||
float_row("##remesh_edge", _L("Target edge"), &m_remesh_target_edge_mm, 0.1f, 20.f, "%.2f mm", true, 0.f,
|
float_row("##remesh_edge", _L("Target edge"), &m_remesh_target_edge_mm, 0.1f, 20.f, "%.2f mm", true, 0.f);
|
||||||
_L("Triangle size the whole model is rebuilt with, in millimetres. Displacement works best on an "
|
hover_tip(_u8L("Triangle size the whole model is rebuilt with, in millimetres. Displacement works best on an "
|
||||||
"even mesh; this is what makes one out of an uneven import."));
|
"even mesh; this is what makes one out of an uneven import."));
|
||||||
|
|
||||||
m_imgui->bbl_checkbox(_L("Keep sharp edges") + "##remesh_sharp", m_remesh_keep_sharp_edges);
|
m_imgui->bbl_checkbox(_L("Keep sharp edges") + "##remesh_sharp", m_remesh_keep_sharp_edges);
|
||||||
hover_tip(_u8L("Holds hard edges and open borders in place while the rest is remeshed. Without it "
|
hover_tip(_u8L("Holds hard edges and open borders in place while the rest is remeshed. Without it "
|
||||||
@@ -7266,10 +7091,11 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
"a cube comes back with wobbly edges."));
|
"a cube comes back with wobbly edges."));
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
m_imgui->disabled_begin(!m_remesh_keep_sharp_edges);
|
m_imgui->disabled_begin(!m_remesh_keep_sharp_edges);
|
||||||
typed_slider("##remesh_sharp_angle", -(1.f + edit_icon_room), &m_remesh_sharp_angle_deg, 5.f, 90.f, "%.0f°", false,
|
ImGui::SetNextItemWidth(-1.f);
|
||||||
_L("How sharp a fold has to be, in degrees, before the remesher treats it as an edge worth "
|
ImGui::SliderFloat("##remesh_sharp_angle", &m_remesh_sharp_angle_deg, 5.f, 90.f, "%.0f°", ImGuiSliderFlags_AlwaysClamp);
|
||||||
"keeping. Lower protects more of the model's shape; higher rebuilds more of it evenly."));
|
|
||||||
m_imgui->disabled_end();
|
m_imgui->disabled_end();
|
||||||
|
hover_tip(_u8L("How sharp a fold has to be, in degrees, before the remesher treats it as an edge worth "
|
||||||
|
"keeping. Lower protects more of the model's shape; higher rebuilds more of it evenly."));
|
||||||
|
|
||||||
m_imgui->disabled_begin(mv == nullptr || busy);
|
m_imgui->disabled_begin(mv == nullptr || busy);
|
||||||
if (ImGui::Button(_u8L("Remesh").c_str()))
|
if (ImGui::Button(_u8L("Remesh").c_str()))
|
||||||
@@ -7297,16 +7123,16 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
"on a layer is a different thing: it blurs the image before it is used."));
|
"on a layer is a different thing: it blurs the image before it is used."));
|
||||||
if (opts.smooth_enabled) {
|
if (opts.smooth_enabled) {
|
||||||
float percent = opts.smooth_strength * 100.f;
|
float percent = opts.smooth_strength * 100.f;
|
||||||
if (float_row("##dispsmooth", _L_CONTEXT("Strength", "Texture Displacement"), &percent, 1.f, 100.f, "%.0f %%", false, 0.f,
|
if (float_row("##dispsmooth", _L_CONTEXT("Strength", "Texture Displacement"), &percent, 1.f, 100.f, "%.0f %%", false, 0.f)) {
|
||||||
_L("How far each pass pulls a vertex towards its neighbours. High values round the "
|
|
||||||
"relief off quickly; low values need more passes but keep more of the detail."))) {
|
|
||||||
opts.smooth_strength = std::clamp(percent / 100.f, 0.01f, 1.f);
|
opts.smooth_strength = std::clamp(percent / 100.f, 0.01f, 1.f);
|
||||||
m_preview_params_dirty = true;
|
m_preview_params_dirty = true;
|
||||||
}
|
}
|
||||||
if (int_row("##dispsmoothit", _L("Passes"), &opts.smooth_iterations, 1, 10, "%d", 0.f,
|
hover_tip(_u8L("How far each pass pulls a vertex towards its neighbours. High values round the "
|
||||||
_L("How many smoothing passes to run. More passes spread the smoothing further across "
|
"relief off quickly; low values need more passes but keep more of the detail."));
|
||||||
"the surface; Strength decides how much each one moves.")))
|
if (int_row("##dispsmoothit", _L("Passes"), &opts.smooth_iterations, 1, 10, "%d", 0.f))
|
||||||
m_preview_params_dirty = true;
|
m_preview_params_dirty = true;
|
||||||
|
hover_tip(_u8L("How many smoothing passes to run. More passes spread the smoothing further across "
|
||||||
|
"the surface; Strength decides how much each one moves."));
|
||||||
|
|
||||||
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Ignore outer ring"), opts.smooth_skip_border);
|
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Ignore outer ring"), opts.smooth_skip_border);
|
||||||
hover_tip(_u8L("Keeps the outer ring of the painted area out of the smoothing. Its neighbours "
|
hover_tip(_u8L("Keeps the outer ring of the painted area out of the smoothing. Its neighbours "
|
||||||
@@ -7349,16 +7175,16 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
int shown_k = auto_budget ? v2_recommendation(*mv).budget_k : opts.v2_max_triangles_k;
|
int shown_k = auto_budget ? v2_recommendation(*mv).budget_k : opts.v2_max_triangles_k;
|
||||||
m_imgui->disabled_begin(auto_budget);
|
m_imgui->disabled_begin(auto_budget);
|
||||||
// TRN Slider value: %d is the triangle budget in thousands
|
// TRN Slider value: %d is the triangle budget in thousands
|
||||||
if (int_row("##v2budget", _L("Budget"), &shown_k, 0, 4000, auto_budget ? _u8L("%d k (auto)").c_str() : "%d k", 0.f,
|
if (int_row("##v2budget", _L("Budget"), &shown_k, 0, 4000, auto_budget ? _u8L("%d k (auto)").c_str() : "%d k", 0.f)) {
|
||||||
_L("How many thousand triangles this bake may spend on the area you painted. "
|
|
||||||
"Relief already baked into the rest of the model is kept on top of it, so a "
|
|
||||||
"second bake somewhere else gets the same budget as the first. Raise it if the "
|
|
||||||
"warning below Resolution says the detail will not fit; 0 keeps every triangle "
|
|
||||||
"the refinement produced, however many that is."))) {
|
|
||||||
opts.v2_max_triangles_k = shown_k;
|
opts.v2_max_triangles_k = shown_k;
|
||||||
m_preview_params_dirty = true;
|
m_preview_params_dirty = true;
|
||||||
}
|
}
|
||||||
m_imgui->disabled_end();
|
m_imgui->disabled_end();
|
||||||
|
hover_tip(_u8L("How many thousand triangles this bake may spend on the area you painted. "
|
||||||
|
"Relief already baked into the rest of the model is kept on top of it, so a "
|
||||||
|
"second bake somewhere else gets the same budget as the first. Raise it if the "
|
||||||
|
"warning below Resolution says the detail will not fit; 0 keeps every triangle "
|
||||||
|
"the refinement produced, however many that is."));
|
||||||
}
|
}
|
||||||
if (SHOW_PIPELINE_DEV_CONTROLS && opts.pipeline_v2) {
|
if (SHOW_PIPELINE_DEV_CONTROLS && opts.pipeline_v2) {
|
||||||
// Keeping the relief above the plate is not a checkbox: it is unconditional, in both pipelines
|
// Keeping the relief above the plate is not a checkbox: it is unconditional, in both pipelines
|
||||||
@@ -7465,28 +7291,26 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
"default. Untick to set both yourself - worth doing for a texture much finer or "
|
"default. Untick to set both yourself - worth doing for a texture much finer or "
|
||||||
"much coarser than the part it sits on."));
|
"much coarser than the part it sits on."));
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
ImGui::SetNextItemWidth(x0 + panel_w - ImGui::GetCursorPosX());
|
||||||
float shown = auto_res ? rec.edge_mm : opts.v2_refine_mm;
|
float shown = auto_res ? rec.edge_mm : opts.v2_refine_mm;
|
||||||
// Held in a local: the format outlives the call that reads it, and the slider and the
|
|
||||||
// range in its tooltip are then formatted the same way.
|
|
||||||
const std::string v2edge_format = auto_res ? _u8L("%.2f mm (auto)") : std::string("%.2f mm");
|
|
||||||
const wxString v2edge_tip =
|
|
||||||
auto_res && rec.edge_mm > 0.f ?
|
|
||||||
from_u8(Slic3r::format(_u8L("Chosen from the size of the model: %1$.2f mm triangles, "
|
|
||||||
"budget %2% k. Untick Auto to set them yourself."),
|
|
||||||
rec.edge_mm, rec.budget_k)) :
|
|
||||||
_L("How fine the mesh is made under the paint, in millimetres. It has to be "
|
|
||||||
"smaller than the detail you want out of the texture - a 0.5 mm groove needs "
|
|
||||||
"triangles well under 0.5 mm. Smaller costs triangles fast: halving it needs "
|
|
||||||
"four times as many, and once they no longer fit the budget the bake simplifies "
|
|
||||||
"back down and the detail goes with it.");
|
|
||||||
m_imgui->disabled_begin(busy || auto_res);
|
m_imgui->disabled_begin(busy || auto_res);
|
||||||
if (typed_slider("##v2edge", x0 + panel_w - ImGui::GetCursorPosX() - edit_icon_room, &shown, 0.02f, 2.f,
|
if (ImGui::SliderFloat("##v2edge", &shown, 0.02f, 2.f, auto_res ? _u8L("%.2f mm (auto)").c_str() : "%.2f mm",
|
||||||
v2edge_format.c_str(), true, v2edge_tip)) {
|
ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic)) {
|
||||||
opts.v2_refine_mm = shown;
|
opts.v2_refine_mm = shown;
|
||||||
m_preview_params_dirty = true;
|
m_preview_params_dirty = true;
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
}
|
}
|
||||||
m_imgui->disabled_end();
|
m_imgui->disabled_end();
|
||||||
|
if (auto_res && rec.edge_mm > 0.f)
|
||||||
|
hover_tip(Slic3r::format(_u8L("Chosen from the size of the model: %1$.2f mm triangles, "
|
||||||
|
"budget %2% k. Untick Auto to set them yourself."),
|
||||||
|
rec.edge_mm, rec.budget_k));
|
||||||
|
else
|
||||||
|
hover_tip(_u8L("How fine the mesh is made under the paint, in millimetres. It has to be "
|
||||||
|
"smaller than the detail you want out of the texture - a 0.5 mm groove needs "
|
||||||
|
"triangles well under 0.5 mm. Smaller costs triangles fast: halving it needs "
|
||||||
|
"four times as many, and once they no longer fit the budget the bake simplifies "
|
||||||
|
"back down and the detail goes with it."));
|
||||||
|
|
||||||
// What this resolution costs over what is painted, against what the budget allows. Refining
|
// What this resolution costs over what is painted, against what the budget allows. Refining
|
||||||
// past the budget is not an error - the bake simplifies back down to it - but the result
|
// past the budget is not an error - the bake simplifies back down to it - but the result
|
||||||
@@ -7518,17 +7342,18 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
m_imgui->text(_L("Triangles"));
|
m_imgui->text(_L("Triangles"));
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + label_w));
|
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), x0 + label_w));
|
||||||
|
ImGui::SetNextItemWidth(x0 + panel_w - ImGui::GetCursorPosX());
|
||||||
m_imgui->disabled_begin(busy);
|
m_imgui->disabled_begin(busy);
|
||||||
if (typed_slider_int("##subdivbudget", x0 + panel_w - ImGui::GetCursorPosX() - edit_icon_room,
|
if (ImGui::SliderInt("##subdivbudget", &m_subdivide_budget_k, 10, 2000, "+%d k",
|
||||||
&m_subdivide_budget_k, 10, 2000, "+%d k", true,
|
ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic)) {
|
||||||
_L("How many thousand triangles the refinement may add to the model. The triangle "
|
|
||||||
"that fits worst is always split first, so even a run that spends the lot has "
|
|
||||||
"spent it where it shows most. Raise it if the relief still looks coarse."))) {
|
|
||||||
if (m_subdivide_editing)
|
if (m_subdivide_editing)
|
||||||
rebuild_subdivide_preview();
|
rebuild_subdivide_preview();
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
}
|
}
|
||||||
m_imgui->disabled_end();
|
m_imgui->disabled_end();
|
||||||
|
hover_tip(_u8L("How many thousand triangles the refinement may add to the model. The triangle "
|
||||||
|
"that fits worst is always split first, so even a run that spends the lot has "
|
||||||
|
"spent it where it shows most. Raise it if the relief still looks coarse."));
|
||||||
}
|
}
|
||||||
|
|
||||||
const float button_h = std::round(frame_h * 1.25f);
|
const float button_h = std::round(frame_h * 1.25f);
|
||||||
@@ -7547,7 +7372,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
|
|||||||
: m_tool_type == ToolType::SMART_FILL ? m_shortcuts_bucket_fill
|
: m_tool_type == ToolType::SMART_FILL ? m_shortcuts_bucket_fill
|
||||||
: m_tool_type == ToolType::BRUSH ? m_shortcuts_brush
|
: m_tool_type == ToolType::BRUSH ? m_shortcuts_brush
|
||||||
: m_tool_type == ToolType::GAP_FILL ? m_shortcuts_gap_fill
|
: m_tool_type == ToolType::GAP_FILL ? m_shortcuts_gap_fill
|
||||||
: m_shortcuts_sliders; // the sliders are there whether or not a painting tool is
|
: std::vector<std::pair<wxString, wxString>>{};
|
||||||
|
|
||||||
ImGui::SetCursorPosY(row_y + (row_h - icon_h) * .5f); // center vertically
|
ImGui::SetCursorPosY(row_y + (row_h - icon_h) * .5f); // center vertically
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(int(m_imgui->scaled(.5f)), style.ItemSpacing.y));
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(int(m_imgui->scaled(.5f)), style.ItemSpacing.y));
|
||||||
|
|||||||
@@ -197,27 +197,6 @@ public:
|
|||||||
};
|
};
|
||||||
static PaintedColors painted_colors(const TriangleMesh &mesh, const TriangleSelector::TriangleSplittingData &paint);
|
static PaintedColors painted_colors(const TriangleMesh &mesh, const TriangleSelector::TriangleSplittingData &paint);
|
||||||
|
|
||||||
// The view while the UV editor's selected islands are highlighted on the model. The highlight lies on the
|
|
||||||
// undisplaced surface, so the Normal view's relief hides it wherever it rises: Normal gives way to Fast
|
|
||||||
// while islands are selected and comes back once none are. A view the user clicks in the View row is
|
|
||||||
// theirs - the pane's background buttons are not a pick, Height only meaning "the texture view".
|
|
||||||
struct IslandViewFollow
|
|
||||||
{
|
|
||||||
bool shown = false; // the highlight, as of the last step()
|
|
||||||
bool switched = false; // Fast was switched on for the highlight, so Normal comes back once it is gone
|
|
||||||
bool keep_normal = false; // the user picked Normal in the View row while the highlight was shown
|
|
||||||
// The view to apply this frame (0 Normal, 1 Fast), or -1 for none. `view` is the one on screen, numbered as
|
|
||||||
// apply_view_mode() takes it; `debug_open` a debug stage on screen, which holds Normal itself.
|
|
||||||
int step(bool shown_now, int view, bool debug_open);
|
|
||||||
// A View row button clicked, the active one included. The row is drawn after step() in the same frame,
|
|
||||||
// so `shown` is this frame's.
|
|
||||||
void picked(int view)
|
|
||||||
{
|
|
||||||
switched = false;
|
|
||||||
keep_normal = shown && view == 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void render_painter_gizmo() override;
|
void render_painter_gizmo() override;
|
||||||
|
|
||||||
// Intercepts mouse input while "Adjust Texture" mode is on (dragging the on-canvas offset/
|
// Intercepts mouse input while "Adjust Texture" mode is on (dragging the on-canvas offset/
|
||||||
@@ -283,9 +262,6 @@ private:
|
|||||||
// Standard is active so what Preview shows is always what Bake will do. Returns true if it actually
|
// Standard is active so what Preview shows is always what Bake will do. Returns true if it actually
|
||||||
// changed something, so the caller can invalidate the preview.
|
// changed something, so the caller can invalidate the preview.
|
||||||
bool apply_standard_mode_presets(ModelVolume *mv);
|
bool apply_standard_mode_presets(ModelVolume *mv);
|
||||||
// Copies the panel's own state (active layer, Standard/Pro) onto the volume, so the project file
|
|
||||||
// can carry it. Cheap and idempotent - called wherever either of the two changes.
|
|
||||||
void store_panel_state();
|
|
||||||
// Standard mode's Bake: remesh to an even density, refine where the texture bends, then displace.
|
// Standard mode's Bake: remesh to an even density, refine where the texture bends, then displace.
|
||||||
// The order matters and is the whole reason this is one button - a height map can only move
|
// The order matters and is the whole reason this is one button - a height map can only move
|
||||||
// existing vertices, so the mesh has to be prepared first, and remeshing after painting would drop
|
// existing vertices, so the mesh has to be prepared first, and remeshing after painting would drop
|
||||||
@@ -428,12 +404,6 @@ private:
|
|||||||
// Per slot: whether the layer's card shows every setting or only Depth, Tile size and Rotation.
|
// Per slot: whether the layer's card shows every setting or only Depth, Tile size and Rotation.
|
||||||
// Panel state only, not saved with the project.
|
// Panel state only, not saved with the project.
|
||||||
std::array<bool, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_layer_expanded{};
|
std::array<bool, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_layer_expanded{};
|
||||||
// The slider whose edit icon was just clicked, as its ImGui id, and the frame it was clicked on.
|
|
||||||
// The row acts on it on the next frame - ImGui's keyboard-focus request only takes effect a frame
|
|
||||||
// later - and clears it; the frame number is what retires a request whose row has gone off screen
|
|
||||||
// in the meantime. Panel state only, not saved with the project.
|
|
||||||
std::string m_slider_type_request;
|
|
||||||
int m_slider_type_request_frame = 0;
|
|
||||||
|
|
||||||
// "Adjust Texture" mode: instead of painting, dragging an on-canvas handle changes the active
|
// "Adjust Texture" mode: instead of painting, dragging an on-canvas handle changes the active
|
||||||
// layer's offset. The handle is a flat panel lying in the paint patch's own tangent plane
|
// layer's offset. The handle is a flat panel lying in the paint patch's own tangent plane
|
||||||
@@ -534,11 +504,6 @@ private:
|
|||||||
// The panel's view modes: 0 Normal, 1 Fast, 2 Checker, 3 Distortion. Shared by the View row and the pane's
|
// The panel's view modes: 0 Normal, 1 Fast, 2 Checker, 3 Distortion. Shared by the View row and the pane's
|
||||||
// background buttons, so both switch views the same way.
|
// background buttons, so both switch views the same way.
|
||||||
void apply_view_mode(int mode);
|
void apply_view_mode(int mode);
|
||||||
// The view on screen, numbered as apply_view_mode() takes it.
|
|
||||||
int view_mode() const;
|
|
||||||
// Run once a frame, after the pane's commands: switches the view as m_island_view says.
|
|
||||||
void follow_island_highlight();
|
|
||||||
IslandViewFollow m_island_view;
|
|
||||||
// The pane header's thumbnail of the active layer's texture, rebuilt only when its image changes.
|
// The pane header's thumbnail of the active layer's texture, rebuilt only when its image changes.
|
||||||
static constexpr int UV_THUMB_PX = 64;
|
static constexpr int UV_THUMB_PX = 64;
|
||||||
const void *m_uv_thumb_source = nullptr;
|
const void *m_uv_thumb_source = nullptr;
|
||||||
@@ -770,8 +735,6 @@ private:
|
|||||||
// in place. Rebuilt whenever the pane's selection differs from the one it was built for.
|
// in place. Rebuilt whenever the pane's selection differs from the one it was built for.
|
||||||
GLModel m_island_overlay_glmodel;
|
GLModel m_island_overlay_glmodel;
|
||||||
std::vector<int> m_island_overlay_selection;
|
std::vector<int> m_island_overlay_selection;
|
||||||
// The pane's selection while the overlay applies - the pane open over an unwrapped LSCM layer - else null.
|
|
||||||
const std::vector<int> *island_highlight_selection();
|
|
||||||
void rebuild_island_overlay(const std::vector<int> &selection);
|
void rebuild_island_overlay(const std::vector<int> &selection);
|
||||||
void render_island_overlay();
|
void render_island_overlay();
|
||||||
// Set on every paint event, cleared when the overlay is rebuilt in render_painter_gizmo(). Kept
|
// Set on every paint event, cleared when the overlay is rebuilt in render_painter_gizmo(). Kept
|
||||||
@@ -952,26 +915,25 @@ private:
|
|||||||
bool m_preview_job_pending = false;
|
bool m_preview_job_pending = false;
|
||||||
void queue_preview_job();
|
void queue_preview_job();
|
||||||
|
|
||||||
// Per-slot GPU thumbnail cache for the layer list panel, each keyed by the image_data it was built from
|
// Per-slot GPU thumbnail cache for the layer list panel, keyed by the image_data pointer that
|
||||||
// (see get_layer_thumbnail()). Held weakly rather than by address, as decode_height_texture()'s cache is:
|
// was current the last time each thumbnail was built (see get_layer_thumbnail()).
|
||||||
// an image freed and replaced by another at the same address must not pass for it.
|
std::array<std::unique_ptr<GLTexture>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails;
|
||||||
std::array<std::unique_ptr<GLTexture>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails;
|
std::array<const void *, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_source{};
|
||||||
std::array<std::weak_ptr<const std::vector<unsigned char>>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_source;
|
|
||||||
// The smoothing each cached thumbnail was built at, so a smoothing change re-uploads it.
|
// The smoothing each cached thumbnail was built at, so a smoothing change re-uploads it.
|
||||||
std::array<float, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_smoothing{};
|
std::array<float, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_smoothing{};
|
||||||
|
|
||||||
// Full-resolution height texture for the preview shader, keyed the same way (see
|
// Full-resolution height texture for the preview shader, keyed the same way (see
|
||||||
// get_layer_height_texture()). A smoothing change re-uploads it, so the fast preview shows the
|
// get_layer_height_texture()). A smoothing change re-uploads it, so the fast preview shows the
|
||||||
// blur the bake will apply.
|
// blur the bake will apply.
|
||||||
std::unique_ptr<GLTexture> m_height_tex;
|
std::unique_ptr<GLTexture> m_height_tex;
|
||||||
std::weak_ptr<const std::vector<unsigned char>> m_height_tex_source;
|
const void *m_height_tex_source = nullptr;
|
||||||
float m_height_tex_smoothing = -1.f;
|
float m_height_tex_smoothing = -1.f;
|
||||||
|
|
||||||
// The same, for the layer's *colour*, which the fast preview quantizes per fragment so it shows
|
// The same, for the layer's *colour*, which the fast preview quantizes per fragment so it shows
|
||||||
// the image at texel resolution rather than at the mesh's. Null for a grayscale texture.
|
// the image at texel resolution rather than at the mesh's. Null for a grayscale texture.
|
||||||
std::unique_ptr<GLTexture> m_color_tex;
|
std::unique_ptr<GLTexture> m_color_tex;
|
||||||
std::weak_ptr<const std::vector<unsigned char>> m_color_tex_source;
|
const void *m_color_tex_source = nullptr;
|
||||||
float m_color_tex_smoothing = -1.f;
|
float m_color_tex_smoothing = -1.f;
|
||||||
|
|
||||||
// Library textures the picker has shown at least once, keyed by file path (see LibraryTexture).
|
// Library textures the picker has shown at least once, keyed by file path (see LibraryTexture).
|
||||||
std::map<std::string, LibraryTexture> m_library_textures;
|
std::map<std::string, LibraryTexture> m_library_textures;
|
||||||
@@ -1009,12 +971,9 @@ private:
|
|||||||
PatchUnwrap m_uv_editor_unwrap;
|
PatchUnwrap m_uv_editor_unwrap;
|
||||||
|
|
||||||
// When set, the panel is a free-floating window the user can drag anywhere (with a title bar to
|
// When set, the panel is a free-floating window the user can drag anywhere (with a title bar to
|
||||||
// grab), instead of being pinned to the right of the gizmo toolbar. Kept in the application
|
// grab), instead of being pinned to the right of the gizmo toolbar. Persisted across gizmo
|
||||||
// config rather than in the project: it describes the user's window, not the model, so opening
|
// open/close within a session, so the choice sticks while working.
|
||||||
// someone else's project must not move their panel.
|
|
||||||
bool m_undocked = false;
|
bool m_undocked = false;
|
||||||
// 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).
|
// 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
|
// ImGui jumps a fixed number of lines per wheel notch, which on tall layer cards reads as a hard
|
||||||
@@ -1057,9 +1016,6 @@ private:
|
|||||||
std::vector<std::pair<wxString, wxString>> m_shortcuts_bucket_fill;
|
std::vector<std::pair<wxString, wxString>> m_shortcuts_bucket_fill;
|
||||||
// Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")}
|
// Contains all shortcuts in the format of {shortcut, description}, e.g. {alt + _L("Left mouse button"), _L("Part_selection")}
|
||||||
std::vector<std::pair<wxString, wxString>> m_shortcuts_gap_fill;
|
std::vector<std::pair<wxString, wxString>> m_shortcuts_gap_fill;
|
||||||
// The two ways every slider in the panel takes a typed value. They hold whatever tool is selected,
|
|
||||||
// so they are tacked onto each of the lists above and stand on their own when no tool is active.
|
|
||||||
std::vector<std::pair<wxString, wxString>> m_shortcuts_sliders;
|
|
||||||
|
|
||||||
// Icons for the panel's icon buttons (tools, views, mapping, tiling, layer actions). Loaded through IconManager with
|
// Icons for the panel's icon buttons (tools, views, mapping, tiling, layer actions). Loaded through IconManager with
|
||||||
// the same colour/monochrome variants the main toolbar uses, so an inactive button shows the icon in
|
// the same colour/monochrome variants the main toolbar uses, so an inactive button shows the icon in
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef slic3r_MultiTaskModel_hpp_
|
||||||
|
#define slic3r_MultiTaskModel_hpp_
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
#include "SysInfoDialog.hpp"
|
||||||
|
#include "I18N.hpp"
|
||||||
|
#include "3DScene.hpp"
|
||||||
|
#include "GUI.hpp"
|
||||||
|
#include "../Utils/UndoRedo.hpp"
|
||||||
|
#include "Plater.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <Eigen/Core>
|
||||||
|
|
||||||
|
#include <wx/clipbrd.h>
|
||||||
|
#include <wx/platinfo.h>
|
||||||
|
#include "GUI_App.hpp"
|
||||||
|
#include "MainFrame.hpp"
|
||||||
|
#include "wxExtensions.hpp"
|
||||||
|
#include "libslic3r/BlacklistedLibraryCheck.hpp"
|
||||||
|
#include "libslic3r/Color.hpp"
|
||||||
|
#include "format.hpp"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// The standard Windows includes.
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <psapi.h>
|
||||||
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
std::string get_main_info(bool format_as_html)
|
||||||
|
{
|
||||||
|
std::stringstream out;
|
||||||
|
|
||||||
|
std::string b_start = format_as_html ? "<b>" : "";
|
||||||
|
std::string b_end = format_as_html ? "</b>" : "";
|
||||||
|
std::string line_end = format_as_html ? "<br>" : "\n";
|
||||||
|
|
||||||
|
if (!format_as_html)
|
||||||
|
out << b_start << (wxGetApp().is_editor() ? SLIC3R_APP_FULL_NAME : GCODEVIEWER_APP_NAME) << b_end << line_end;
|
||||||
|
out << b_start << "Version: " << b_end << SLIC3R_VERSION << line_end;
|
||||||
|
out << b_start << "Build: " << b_end << (wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID) << line_end;
|
||||||
|
out << line_end;
|
||||||
|
out << b_start << "Operating System: " << b_end << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << line_end;
|
||||||
|
out << b_start << "System Architecture: " << b_end << wxPlatformInfo::Get().GetArchName() << line_end;
|
||||||
|
out << b_start <<
|
||||||
|
#if defined _WIN32
|
||||||
|
"Windows Version: "
|
||||||
|
#else
|
||||||
|
// Hopefully some kind of unix / linux.
|
||||||
|
"System Version: "
|
||||||
|
#endif
|
||||||
|
<< b_end << wxPlatformInfo::Get().GetOperatingSystemDescription() << line_end;
|
||||||
|
out << b_start << "Total RAM size [MB]: " << b_end << Slic3r::format_memsize_MB(Slic3r::total_physical_memory());
|
||||||
|
|
||||||
|
return out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_mem_info(bool format_as_html)
|
||||||
|
{
|
||||||
|
std::stringstream out;
|
||||||
|
|
||||||
|
std::string b_start = format_as_html ? "<b>" : "";
|
||||||
|
std::string b_end = format_as_html ? "</b>" : "";
|
||||||
|
std::string line_end = format_as_html ? "<br>" : "\n";
|
||||||
|
|
||||||
|
std::string mem_info_str = log_memory_info(true);
|
||||||
|
std::istringstream mem_info(mem_info_str);
|
||||||
|
std::string value;
|
||||||
|
while (std::getline(mem_info, value, ':')) {
|
||||||
|
out << b_start << (value+": ") << b_end;
|
||||||
|
std::getline(mem_info, value, ';');
|
||||||
|
out << value << line_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Slic3r::UndoRedo::Stack &stack = wxGetApp().plater()->undo_redo_stack_main();
|
||||||
|
out << b_start << "RAM size reserved for the Undo / Redo stack: " << b_end << Slic3r::format_memsize_MB(stack.get_memory_limit()) << line_end;
|
||||||
|
out << b_start << "RAM size occupied by the Undo / Redo stack: " << b_end << Slic3r::format_memsize_MB(stack.memsize()) << line_end << line_end;
|
||||||
|
|
||||||
|
return out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
SysInfoDialog::SysInfoDialog()
|
||||||
|
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, (wxGetApp().is_editor() ? wxString(SLIC3R_APP_FULL_NAME) : wxString(GCODEVIEWER_APP_NAME)) + " - " + _L("System Information"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||||
|
{
|
||||||
|
wxColour bgr_clr = wxGetApp().get_window_default_clr();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||||
|
SetBackgroundColour(bgr_clr);
|
||||||
|
SetFont(wxGetApp().normal_font());
|
||||||
|
|
||||||
|
wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
hsizer->SetMinSize(wxSize(50 * wxGetApp().em_unit(), -1));
|
||||||
|
|
||||||
|
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
main_sizer->Add(hsizer, 1, wxEXPAND | wxALL, 10);
|
||||||
|
|
||||||
|
// logo
|
||||||
|
m_logo_bmp = ScalableBitmap(this, wxGetApp().logo_name(), 192);
|
||||||
|
m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bmp.bmp());
|
||||||
|
hsizer->Add(m_logo, 0, wxALIGN_CENTER_VERTICAL);
|
||||||
|
|
||||||
|
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
hsizer->Add(vsizer, 1, wxEXPAND|wxLEFT, 20);
|
||||||
|
|
||||||
|
// title
|
||||||
|
{
|
||||||
|
wxStaticText* title = new wxStaticText(this, wxID_ANY, wxGetApp().is_editor() ? SLIC3R_APP_FULL_NAME : GCODEVIEWER_APP_NAME, wxDefaultPosition, wxDefaultSize);
|
||||||
|
wxFont title_font = wxGetApp().bold_font();
|
||||||
|
title_font.SetFamily(wxFONTFAMILY_ROMAN);
|
||||||
|
title_font.SetPointSize(22);
|
||||||
|
title->SetFont(title_font);
|
||||||
|
vsizer->Add(title, 0, wxEXPAND | wxTOP, wxGetApp().em_unit()/*50*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
// main_info_text
|
||||||
|
wxFont font = get_default_font(this);
|
||||||
|
const auto text_clr = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||||
|
auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
|
||||||
|
auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
|
||||||
|
|
||||||
|
const int fs = font.GetPointSize() - 1;
|
||||||
|
int size[] = { static_cast<int>(fs*1.5), static_cast<int>(fs*1.4), static_cast<int>(fs*1.3), fs, fs, fs, fs };
|
||||||
|
|
||||||
|
m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER);
|
||||||
|
{
|
||||||
|
m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
|
||||||
|
m_html->SetBorders(2);
|
||||||
|
const auto text = wxString::Format(
|
||||||
|
"<html>"
|
||||||
|
"<body bgcolor= %s link= %s>"
|
||||||
|
"<font color=%s>"
|
||||||
|
"%s"
|
||||||
|
"</font>"
|
||||||
|
"</body>"
|
||||||
|
"</html>", bgr_clr_str, text_clr_str, text_clr_str,
|
||||||
|
get_main_info(true));
|
||||||
|
m_html->SetPage(text);
|
||||||
|
vsizer->Add(m_html, 1, wxEXPAND | wxBOTTOM, wxGetApp().em_unit());
|
||||||
|
}
|
||||||
|
|
||||||
|
// opengl_info
|
||||||
|
m_opengl_info_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
|
||||||
|
{
|
||||||
|
m_opengl_info_html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit()));
|
||||||
|
m_opengl_info_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
|
||||||
|
m_opengl_info_html->SetBorders(10);
|
||||||
|
wxString blacklisted_libraries_message;
|
||||||
|
#ifdef WIN32
|
||||||
|
std::wstring blacklisted_libraries = BlacklistedLibraryCheck::get_instance().get_blacklisted_string().c_str();
|
||||||
|
if (! blacklisted_libraries.empty())
|
||||||
|
blacklisted_libraries_message = wxString("<br><b>") + _L("Blacklisted libraries loaded into OrcaSlicer process:") + "</b><br>" + blacklisted_libraries;
|
||||||
|
#endif // WIN32
|
||||||
|
const auto text = GUI::format_wxstr(
|
||||||
|
"<html>"
|
||||||
|
"<body bgcolor= %s link= %s>"
|
||||||
|
"<font color=%s>"
|
||||||
|
"%s<br>%s<br>%s<br>%s"
|
||||||
|
"</font>"
|
||||||
|
"</body>"
|
||||||
|
"</html>", bgr_clr_str, text_clr_str, text_clr_str,
|
||||||
|
blacklisted_libraries_message,
|
||||||
|
get_mem_info(true), wxGetApp().get_gl_info(false),
|
||||||
|
"<b>" + _L("SIMD is supported:") + "</b> " + Eigen::SimdInstructionSetsInUse());
|
||||||
|
|
||||||
|
m_opengl_info_html->SetPage(text);
|
||||||
|
main_sizer->Add(m_opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK);
|
||||||
|
m_btn_copy_to_clipboard = new wxButton(this, wxID_ANY, _L("Copy to Clipboard"), wxDefaultPosition, wxDefaultSize);
|
||||||
|
|
||||||
|
buttons->Insert(0, m_btn_copy_to_clipboard, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
|
||||||
|
m_btn_copy_to_clipboard->Bind(wxEVT_BUTTON, &SysInfoDialog::onCopyToClipboard, this);
|
||||||
|
|
||||||
|
this->SetEscapeId(wxID_OK);
|
||||||
|
this->Bind(wxEVT_BUTTON, &SysInfoDialog::onCloseDialog, this, wxID_OK);
|
||||||
|
main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
|
||||||
|
|
||||||
|
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||||
|
|
||||||
|
// this->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this);
|
||||||
|
// logo->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this);
|
||||||
|
|
||||||
|
SetSizer(main_sizer);
|
||||||
|
main_sizer->SetSizeHints(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SysInfoDialog::on_dpi_changed(const wxRect &suggested_rect)
|
||||||
|
{
|
||||||
|
m_logo_bmp.msw_rescale();
|
||||||
|
m_logo->SetBitmap(m_logo_bmp.bmp());
|
||||||
|
|
||||||
|
wxFont font = get_default_font(this);
|
||||||
|
const int fs = font.GetPointSize() - 1;
|
||||||
|
int font_size[] = { static_cast<int>(fs*1.5), static_cast<int>(fs*1.4), static_cast<int>(fs*1.3), fs, fs, fs, fs };
|
||||||
|
|
||||||
|
m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), font_size);
|
||||||
|
m_html->Refresh();
|
||||||
|
|
||||||
|
const int& em = em_unit();
|
||||||
|
|
||||||
|
msw_buttons_rescale(this, em, { wxID_OK, m_btn_copy_to_clipboard->GetId() });
|
||||||
|
|
||||||
|
m_opengl_info_html->SetMinSize(wxSize(-1, 16 * em));
|
||||||
|
m_opengl_info_html->SetFonts(font.GetFaceName(), font.GetFaceName(), font_size);
|
||||||
|
m_opengl_info_html->Refresh();
|
||||||
|
|
||||||
|
const wxSize& size = wxSize(65 * em, 55 * em);
|
||||||
|
|
||||||
|
SetMinSize(size);
|
||||||
|
Fit();
|
||||||
|
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SysInfoDialog::onCopyToClipboard(wxEvent &)
|
||||||
|
{
|
||||||
|
wxTheClipboard->Open();
|
||||||
|
const auto text = get_main_info(false) + "\n" + wxGetApp().get_gl_info(true);
|
||||||
|
wxTheClipboard->SetData(new wxTextDataObject(text));
|
||||||
|
wxTheClipboard->Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SysInfoDialog::onCloseDialog(wxEvent &)
|
||||||
|
{
|
||||||
|
this->EndModal(wxID_CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef slic3r_GUI_SysInfoDialog_hpp_
|
||||||
|
#define slic3r_GUI_SysInfoDialog_hpp_
|
||||||
|
|
||||||
|
#include <wx/event.h>
|
||||||
|
#include <wx/wx.h>
|
||||||
|
#include <wx/html/htmlwin.h>
|
||||||
|
|
||||||
|
#include "GUI_Utils.hpp"
|
||||||
|
#include "wxExtensions.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
class SysInfoDialog : public DPIDialog
|
||||||
|
{
|
||||||
|
ScalableBitmap m_logo_bmp;
|
||||||
|
wxStaticBitmap* m_logo;
|
||||||
|
wxHtmlWindow* m_opengl_info_html;
|
||||||
|
wxHtmlWindow* m_html;
|
||||||
|
|
||||||
|
wxButton* m_btn_copy_to_clipboard;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SysInfoDialog();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onCopyToClipboard(wxEvent &);
|
||||||
|
void onCloseDialog(wxEvent &);
|
||||||
|
};
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -939,8 +939,7 @@ void UVEditorCanvas::on_mouse(wxMouseEvent &evt)
|
|||||||
m_active_edge = (hit.first >= 0 && is_edge_selected(hit)) ? hit : std::pair<int, int>{ -1, -1 };
|
m_active_edge = (hit.first >= 0 && is_edge_selected(hit)) ? hit : std::pair<int, int>{ -1, -1 };
|
||||||
m_gesture = (m_active_edge.first >= 0) ? Gesture::MoveEdge : Gesture::Pan;
|
m_gesture = (m_active_edge.first >= 0) ? Gesture::MoveEdge : Gesture::Pan;
|
||||||
} else {
|
} else {
|
||||||
const std::vector<int> selection_before = m_selection;
|
const int hit = island_at(uv);
|
||||||
const int hit = island_at(uv);
|
|
||||||
if (hit >= 0) {
|
if (hit >= 0) {
|
||||||
if (evt.ShiftDown()) {
|
if (evt.ShiftDown()) {
|
||||||
// Shift adds to the selection (and makes the clicked one the new primary).
|
// Shift adds to the selection (and makes the clicked one the new primary).
|
||||||
@@ -970,10 +969,6 @@ void UVEditorCanvas::on_mouse(wxMouseEvent &evt)
|
|||||||
m_selected_island = -1;
|
m_selected_island = -1;
|
||||||
}
|
}
|
||||||
m_gesture = (m_selected_island >= 0) ? Gesture::MoveIsland : Gesture::Pan;
|
m_gesture = (m_selected_island >= 0) ? Gesture::MoveIsland : Gesture::Pan;
|
||||||
// The owner shows the selection on the model and would not hear of this click otherwise in time: a
|
|
||||||
// click that clears the selection starts no gesture, and one that sets it reports only on release.
|
|
||||||
if (m_selection != selection_before)
|
|
||||||
run_command(Command::SelectionChanged);
|
|
||||||
}
|
}
|
||||||
grab_mouse();
|
grab_mouse();
|
||||||
Refresh();
|
Refresh();
|
||||||
|
|||||||
@@ -108,12 +108,10 @@ public:
|
|||||||
// the command callback - the canvas has the selection and the view, the gizmo has the layer.
|
// the command callback - the canvas has the selection and the view, the gizmo has the layer.
|
||||||
// `value` carries the new setting for the Set* commands (an angle, a flag as 0/1, or an index into
|
// `value` carries the new setting for the Set* commands (an angle, a flag as 0/1, or an index into
|
||||||
// SelectMode / Background) and is 0 otherwise.
|
// SelectMode / Background) and is 0 otherwise.
|
||||||
// SelectionChanged is sent by the canvas itself when a click changes the island selection. It carries
|
|
||||||
// nothing: the owner reads selected_islands() back, and only needs telling that it should look.
|
|
||||||
enum class Command {
|
enum class Command {
|
||||||
FrameAll, ToggleSnap, AverageScale, CutSelectedIsland, ProjectFromView, JoinSelected, UnjoinSelected,
|
FrameAll, ToggleSnap, AverageScale, CutSelectedIsland, ProjectFromView, JoinSelected, UnjoinSelected,
|
||||||
Unwrap, SetSeamAngle, SetConnectIslands, SetSelectMode, SetMarkSeams, SetSeamPath, ClearSeams, ClearUVEdits,
|
Unwrap, SetSeamAngle, SetConnectIslands, SetSelectMode, SetMarkSeams, SetSeamPath, ClearSeams, ClearUVEdits,
|
||||||
SetBackground, PickTexture, PaneClosed, SelectionChanged
|
SetBackground, PickTexture, PaneClosed
|
||||||
};
|
};
|
||||||
void run_command(Command cmd, float value = 0.f);
|
void run_command(Command cmd, float value = 0.f);
|
||||||
using CommandFn = std::function<void(Command, float)>;
|
using CommandFn = std::function<void(Command, float)>;
|
||||||
|
|||||||
@@ -82,11 +82,6 @@ ComboBox::ComboBox(wxWindow *parent,
|
|||||||
e.SetId(GetId());
|
e.SetId(GetId());
|
||||||
GetEventHandler()->ProcessEvent(e);
|
GetEventHandler()->ProcessEvent(e);
|
||||||
});
|
});
|
||||||
drop.Bind(EVT_DROPDOWN_ITEM_ACTION, [this](wxCommandEvent &e) {
|
|
||||||
e.SetEventObject(this);
|
|
||||||
e.SetId(GetId());
|
|
||||||
GetEventHandler()->ProcessEvent(e);
|
|
||||||
});
|
|
||||||
drop.Bind(EVT_DISMISS, [this](auto &) {
|
drop.Bind(EVT_DISMISS, [this](auto &) {
|
||||||
drop_down = false;
|
drop_down = false;
|
||||||
wxCommandEvent e(wxEVT_COMBOBOX_CLOSEUP);
|
wxCommandEvent e(wxEVT_COMBOBOX_CLOSEUP);
|
||||||
@@ -256,7 +251,7 @@ int ComboBox::Append(const wxString &text,
|
|||||||
int style)
|
int style)
|
||||||
{
|
{
|
||||||
auto valid_bit_map = bitmap.IsOk() ? bitmap : wxNullBitmap;
|
auto valid_bit_map = bitmap.IsOk() ? bitmap : wxNullBitmap;
|
||||||
Item item{text, wxEmptyString, valid_bit_map, valid_bit_map, wxNullBitmap, clientData, group_key, group_label};
|
Item item{text, wxEmptyString, valid_bit_map, valid_bit_map, clientData, group_key, group_label};
|
||||||
item.style = style;
|
item.style = style;
|
||||||
items.push_back(item);
|
items.push_back(item);
|
||||||
SetClientDataType(wxClientData_Void);
|
SetClientDataType(wxClientData_Void);
|
||||||
@@ -302,12 +297,6 @@ void ComboBox::ForceDropdownOpen()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComboBox::CloseDropdown()
|
|
||||||
{
|
|
||||||
if (drop.IsShown())
|
|
||||||
drop.DismissAndNotify();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int ComboBox::GetCount() const { return items.size(); }
|
unsigned int ComboBox::GetCount() const { return items.size(); }
|
||||||
|
|
||||||
void ComboBox::set_replace_text(wxString text, wxString image_name)
|
void ComboBox::set_replace_text(wxString text, wxString image_name)
|
||||||
@@ -361,13 +350,6 @@ void ComboBox::SetItemBitmap(unsigned int n, wxBitmap const &bitmap)
|
|||||||
drop.Invalidate();
|
drop.Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ComboBox::SetItemActionBitmap(unsigned int n, wxBitmap const &bitmap)
|
|
||||||
{
|
|
||||||
if (n >= items.size()) return;
|
|
||||||
items[n].action_icon = bitmap.IsOk() ? bitmap : wxNullBitmap;
|
|
||||||
drop.Invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
int ComboBox::DoInsertItems(const wxArrayStringsAdapter &items,
|
int ComboBox::DoInsertItems(const wxArrayStringsAdapter &items,
|
||||||
unsigned int pos,
|
unsigned int pos,
|
||||||
void ** clientData,
|
void ** clientData,
|
||||||
@@ -375,7 +357,7 @@ int ComboBox::DoInsertItems(const wxArrayStringsAdapter &items,
|
|||||||
{
|
{
|
||||||
if (pos > this->items.size()) return -1;
|
if (pos > this->items.size()) return -1;
|
||||||
for (int i = 0; i < items.GetCount(); ++i) {
|
for (int i = 0; i < items.GetCount(); ++i) {
|
||||||
Item item { items[i], wxEmptyString, wxNullBitmap, wxNullBitmap, wxNullBitmap, clientData ? clientData[i] : NULL };
|
Item item { items[i], wxEmptyString, wxNullBitmap, wxNullBitmap, clientData ? clientData[i] : NULL };
|
||||||
this->items.insert(this->items.begin() + pos, item);
|
this->items.insert(this->items.begin() + pos, item);
|
||||||
++pos;
|
++pos;
|
||||||
}
|
}
|
||||||
@@ -437,18 +419,16 @@ void ComboBox::keyDown(wxKeyEvent& event)
|
|||||||
case WXK_UP:
|
case WXK_UP:
|
||||||
case WXK_DOWN:
|
case WXK_DOWN:
|
||||||
case WXK_LEFT:
|
case WXK_LEFT:
|
||||||
case WXK_RIGHT: {
|
case WXK_RIGHT:
|
||||||
// Step over disabled rows: the mouse cannot select them either.
|
if ((event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_LEFT) && GetSelection() > 0) {
|
||||||
const int step = (event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_LEFT) ? -1 : 1;
|
SetSelection(GetSelection() - 1);
|
||||||
int n = GetSelection() + step;
|
} else if ((event.GetKeyCode() == WXK_DOWN || event.GetKeyCode() == WXK_RIGHT) && GetSelection() + 1 < items.size()) {
|
||||||
while (n >= 0 && n < int(items.size()) && (items[n].style & DD_ITEM_STYLE_DISABLED))
|
SetSelection(GetSelection() + 1);
|
||||||
n += step;
|
} else {
|
||||||
if (n < 0 || n >= int(items.size()))
|
|
||||||
break;
|
break;
|
||||||
SetSelection(n);
|
}
|
||||||
sendComboBoxEvent();
|
sendComboBoxEvent();
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case WXK_TAB:
|
case WXK_TAB:
|
||||||
HandleAsNavigationKey(event);
|
HandleAsNavigationKey(event);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -96,12 +96,10 @@ public:
|
|||||||
|
|
||||||
wxBitmap GetItemBitmap(unsigned int n);
|
wxBitmap GetItemBitmap(unsigned int n);
|
||||||
void SetItemBitmap(unsigned int n, wxBitmap const &bitmap);
|
void SetItemBitmap(unsigned int n, wxBitmap const &bitmap);
|
||||||
void SetItemActionBitmap(unsigned int n, wxBitmap const &bitmap);
|
|
||||||
bool is_drop_down(){return drop_down;}
|
bool is_drop_down(){return drop_down;}
|
||||||
void DeleteOneItem(unsigned int pos) { DoDeleteOneItem(pos); }
|
void DeleteOneItem(unsigned int pos) { DoDeleteOneItem(pos); }
|
||||||
|
|
||||||
void ForceDropdownOpen();
|
void ForceDropdownOpen();
|
||||||
void CloseDropdown();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual int DoInsertItems(const wxArrayStringsAdapter &items,
|
virtual int DoInsertItems(const wxArrayStringsAdapter &items,
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
#include "slic3r/GUI/wxExtensions.hpp"
|
#include "slic3r/GUI/wxExtensions.hpp"
|
||||||
|
|
||||||
wxDEFINE_EVENT(EVT_DISMISS, wxCommandEvent);
|
wxDEFINE_EVENT(EVT_DISMISS, wxCommandEvent);
|
||||||
wxDEFINE_EVENT(EVT_DROPDOWN_ITEM_ACTION, wxCommandEvent);
|
|
||||||
|
|
||||||
BEGIN_EVENT_TABLE(DropDown, PopupWindow)
|
BEGIN_EVENT_TABLE(DropDown, PopupWindow)
|
||||||
|
|
||||||
@@ -243,19 +242,6 @@ static wxSize GetBmpSize(wxBitmap & bmp)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// The x-range [first, second) of the column at the right of the rows that holds the items' action
|
|
||||||
// icons, empty when no item has one. render() draws the icons in it and mouseReleased() hit-tests
|
|
||||||
// clicks against it, so the drawn icon and its clickable area always match.
|
|
||||||
std::pair<int, int> DropDown::actionColumn() const
|
|
||||||
{
|
|
||||||
if (actionIconWidth <= 0)
|
|
||||||
return {0, 0};
|
|
||||||
int right = rowSize.x;
|
|
||||||
if (rowSize.y * count > GetSize().y) // position bar
|
|
||||||
right -= 6;
|
|
||||||
return {right - actionIconWidth - FromDIP(16), right};
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _DrawSplitItem(const wxWindow* w, wxDC& dc, wxString split_text, wxPoint start_pt, int item_width, int item_height)
|
static void _DrawSplitItem(const wxWindow* w, wxDC& dc, wxString split_text, wxPoint start_pt, int item_width, int item_height)
|
||||||
{
|
{
|
||||||
// save dc
|
// save dc
|
||||||
@@ -385,10 +371,6 @@ void DropDown::render(wxDC &dc)
|
|||||||
rcContent.width -= szBmp.x + 5;
|
rcContent.width -= szBmp.x + 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto action_column = actionColumn();
|
|
||||||
if (action_column.second > action_column.first)
|
|
||||||
rcContent.width = action_column.first - rcContent.x;
|
|
||||||
|
|
||||||
std::set<wxString> groups;
|
std::set<wxString> groups;
|
||||||
// draw texts & icons
|
// draw texts & icons
|
||||||
int index = 0;
|
int index = 0;
|
||||||
@@ -452,24 +434,13 @@ void DropDown::render(wxDC &dc)
|
|||||||
auto text = group.IsEmpty()
|
auto text = group.IsEmpty()
|
||||||
? (item.group_key.IsEmpty() ? item.text : item.group_label)
|
? (item.group_key.IsEmpty() ? item.text : item.group_label)
|
||||||
: item.text;
|
: item.text;
|
||||||
if (item.action_icon.IsOk()) {
|
|
||||||
auto action_size = GetBmpSize(item.action_icon);
|
|
||||||
wxPoint action_pt(action_column.second - action_size.x - FromDIP(8),
|
|
||||||
rcContent.y + (rcContent.height - action_size.y) / 2);
|
|
||||||
dc.DrawBitmap(item.action_icon, action_pt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!text_off && !text.IsEmpty()) {
|
if (!text_off && !text.IsEmpty()) {
|
||||||
wxSize tSize = dc.GetMultiLineTextExtent(text);
|
wxSize tSize = dc.GetMultiLineTextExtent(text);
|
||||||
if (pt.x + tSize.x > rcContent.GetRight()) {
|
if (pt.x + tSize.x > rcContent.GetRight()) {
|
||||||
int max_text_width = rcContent.GetRight() - pt.x;
|
if (is_hover && item.tip.IsEmpty())
|
||||||
if (max_text_width <= 0) {
|
SetToolTip(text);
|
||||||
text.clear();
|
text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END,
|
||||||
} else {
|
rcContent.GetRight() - pt.x);
|
||||||
if (is_hover && item.tip.IsEmpty())
|
|
||||||
SetToolTip(text);
|
|
||||||
text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, max_text_width);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pt.y += (rcContent.height - textSize.y) / 2;
|
pt.y += (rcContent.height - textSize.y) / 2;
|
||||||
dc.SetFont(GetFont());
|
dc.SetFont(GetFont());
|
||||||
@@ -558,7 +529,6 @@ void DropDown::messureSize()
|
|||||||
if (!need_sync) return;
|
if (!need_sync) return;
|
||||||
textSize = wxSize();
|
textSize = wxSize();
|
||||||
iconSize = wxSize();
|
iconSize = wxSize();
|
||||||
actionIconWidth = 0;
|
|
||||||
count = 0;
|
count = 0;
|
||||||
wxClientDC dc(GetParent() ? GetParent() : this);
|
wxClientDC dc(GetParent() ? GetParent() : this);
|
||||||
dc.SetFont(GetFont());
|
dc.SetFont(GetFont());
|
||||||
@@ -595,13 +565,8 @@ void DropDown::messureSize()
|
|||||||
size1.x += size2.x + (text_off ? 0 : 5);
|
size1.x += size2.x + (text_off ? 0 : 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item.action_icon.IsOk())
|
|
||||||
actionIconWidth = std::max(actionIconWidth, GetBmpSize(item.action_icon).x);
|
|
||||||
if (size1.x > textSize.x) textSize = size1;
|
if (size1.x > textSize.x) textSize = size1;
|
||||||
}
|
}
|
||||||
// Every row leaves room for the action column, as render() and actionColumn() lay it out.
|
|
||||||
if (actionIconWidth > 0)
|
|
||||||
textSize.x += actionIconWidth + FromDIP(16);
|
|
||||||
if (!align_icon) iconSize.x = 0;
|
if (!align_icon) iconSize.x = 0;
|
||||||
wxSize szContent = textSize;
|
wxSize szContent = textSize;
|
||||||
if (szContent.x < FromDIP(120) && !use_content_width)
|
if (szContent.x < FromDIP(120) && !use_content_width)
|
||||||
@@ -671,11 +636,6 @@ void DropDown::messureSize()
|
|||||||
e.SetId(GetId());
|
e.SetId(GetId());
|
||||||
GetEventHandler()->ProcessEvent(e);
|
GetEventHandler()->ProcessEvent(e);
|
||||||
});
|
});
|
||||||
subDropDown->Bind(EVT_DROPDOWN_ITEM_ACTION, [this](wxCommandEvent &e) {
|
|
||||||
e.SetEventObject(this);
|
|
||||||
e.SetId(GetId());
|
|
||||||
GetEventHandler()->ProcessEvent(e);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
need_sync = false;
|
need_sync = false;
|
||||||
}
|
}
|
||||||
@@ -754,15 +714,6 @@ void DropDown::mouseReleased(wxMouseEvent& event)
|
|||||||
if (hover_item < 0)
|
if (hover_item < 0)
|
||||||
return;
|
return;
|
||||||
if (hover_item >= 0 && (subDropDown == nullptr || subDropDown->group.empty())) { // not moved
|
if (hover_item >= 0 && (subDropDown == nullptr || subDropDown->group.empty())) { // not moved
|
||||||
int index = hoverIndex();
|
|
||||||
if (index >= 0 && items[index].action_icon.IsOk()) {
|
|
||||||
const auto action_column = actionColumn();
|
|
||||||
if (event.GetX() >= action_column.first && event.GetX() < action_column.second) {
|
|
||||||
sendDropDownActionEvent(index);
|
|
||||||
paintNow();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sendDropDownEvent();
|
sendDropDownEvent();
|
||||||
if (mainDropDown)
|
if (mainDropDown)
|
||||||
mainDropDown->hover_item = -1; // To Dismiss mainDropDown
|
mainDropDown->hover_item = -1; // To Dismiss mainDropDown
|
||||||
@@ -879,17 +830,6 @@ void DropDown::sendDropDownEvent()
|
|||||||
GetEventHandler()->ProcessEvent(event);
|
GetEventHandler()->ProcessEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DropDown::sendDropDownActionEvent(int index)
|
|
||||||
{
|
|
||||||
if (index < 0 || index >= int(items.size()) || (items[index].style & DD_ITEM_STYLE_DISABLED))
|
|
||||||
return;
|
|
||||||
wxCommandEvent event(EVT_DROPDOWN_ITEM_ACTION, GetId());
|
|
||||||
event.SetEventObject(this);
|
|
||||||
event.SetInt(index);
|
|
||||||
event.SetString(items[index].text);
|
|
||||||
GetEventHandler()->ProcessEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DropDown::Dismiss()
|
void DropDown::Dismiss()
|
||||||
{
|
{
|
||||||
if (subDropDown && subDropDown->IsShown())
|
if (subDropDown && subDropDown->IsShown())
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
#include <wx/event.h>
|
#include <wx/event.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <utility>
|
|
||||||
#include <wx/gdicmn.h>
|
#include <wx/gdicmn.h>
|
||||||
#include "slic3r/GUI/Widgets/StateColor.hpp"
|
#include "slic3r/GUI/Widgets/StateColor.hpp"
|
||||||
#include <boost/date_time/posix_time/ptime.hpp>
|
#include <boost/date_time/posix_time/ptime.hpp>
|
||||||
@@ -28,7 +27,6 @@ class wxWindow;
|
|||||||
#define DD_ITEM_STYLE_DIMMED 0x0004 // gray text, but still selectable
|
#define DD_ITEM_STYLE_DIMMED 0x0004 // gray text, but still selectable
|
||||||
|
|
||||||
wxDECLARE_EVENT(EVT_DISMISS, wxCommandEvent);
|
wxDECLARE_EVENT(EVT_DISMISS, wxCommandEvent);
|
||||||
wxDECLARE_EVENT(EVT_DROPDOWN_ITEM_ACTION, wxCommandEvent);
|
|
||||||
|
|
||||||
class DropDown : public PopupWindow
|
class DropDown : public PopupWindow
|
||||||
{
|
{
|
||||||
@@ -39,7 +37,6 @@ public:
|
|||||||
wxString text_static_tips;// display static tips for TextInput.eg.PrinterInfoBox
|
wxString text_static_tips;// display static tips for TextInput.eg.PrinterInfoBox
|
||||||
wxBitmap icon;
|
wxBitmap icon;
|
||||||
wxBitmap icon_textctrl;// display icon for TextInput.eg.PrinterInfoBox
|
wxBitmap icon_textctrl;// display icon for TextInput.eg.PrinterInfoBox
|
||||||
wxBitmap action_icon;
|
|
||||||
void * data{nullptr};
|
void * data{nullptr};
|
||||||
wxString group_key{};
|
wxString group_key{};
|
||||||
wxString group_label{};
|
wxString group_label{};
|
||||||
@@ -68,7 +65,6 @@ private:
|
|||||||
|
|
||||||
wxSize textSize;
|
wxSize textSize;
|
||||||
wxSize iconSize;
|
wxSize iconSize;
|
||||||
int actionIconWidth = 0;
|
|
||||||
wxSize rowSize;
|
wxSize rowSize;
|
||||||
|
|
||||||
StateHandler state_handler;
|
StateHandler state_handler;
|
||||||
@@ -139,7 +135,6 @@ private:
|
|||||||
int hoverIndex();
|
int hoverIndex();
|
||||||
|
|
||||||
int selectedItem();
|
int selectedItem();
|
||||||
std::pair<int, int> actionColumn() const;
|
|
||||||
|
|
||||||
friend class ComboBox;
|
friend class ComboBox;
|
||||||
void messureSize();
|
void messureSize();
|
||||||
@@ -153,7 +148,6 @@ private:
|
|||||||
void mouseWheelMoved(wxMouseEvent &event);
|
void mouseWheelMoved(wxMouseEvent &event);
|
||||||
|
|
||||||
void sendDropDownEvent();
|
void sendDropDownEvent();
|
||||||
void sendDropDownActionEvent(int index);
|
|
||||||
|
|
||||||
|
|
||||||
DECLARE_EVENT_TABLE()
|
DECLARE_EVENT_TABLE()
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ webkit_web_view_run_javascript_finish (WebKitWebView
|
|||||||
GError **error);
|
GError **error);
|
||||||
WEBKIT_API void
|
WEBKIT_API void
|
||||||
webkit_javascript_result_unref (WebKitJavascriptResult *js_result);
|
webkit_javascript_result_unref (WebKitJavascriptResult *js_result);
|
||||||
|
WEBKIT_API guint
|
||||||
|
webkit_get_major_version (void);
|
||||||
|
WEBKIT_API guint
|
||||||
|
webkit_get_minor_version (void);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -401,6 +405,29 @@ bool WebView::NeedsRecreateOnShow()
|
|||||||
return false;
|
return false;
|
||||||
#endif
|
#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
|
#if wxUSE_WEBVIEW_EDGE
|
||||||
bool WebView::CheckWebViewRuntime()
|
bool WebView::CheckWebViewRuntime()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,6 +30,16 @@ public:
|
|||||||
// ignoring every navigation. A panel that gets true here recreates its view on first Show().
|
// ignoring every navigation. A panel that gets true here recreates its view on first Show().
|
||||||
static bool NeedsRecreateOnShow();
|
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();
|
static void RecreateAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef slic3r_wxinit_hpp_
|
||||||
|
#define slic3r_wxinit_hpp_
|
||||||
|
|
||||||
|
#include <wx/wx.h>
|
||||||
|
#include <wx/intl.h>
|
||||||
|
#include <wx/html/htmlwin.h>
|
||||||
|
|
||||||
|
// Perl redefines a _ macro, so we undef this one
|
||||||
|
#undef _
|
||||||
|
|
||||||
|
// We do want to use translation however, so define it as __ so we can do a find/replace
|
||||||
|
// later when we no longer need to undef _
|
||||||
|
#define __(s) wxGetTranslation((s))
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef slic3r_GUI_Profile_hpp_
|
||||||
|
#define slic3r_GUI_Profile_hpp_
|
||||||
|
|
||||||
|
// Profiling support using the Shiny intrusive profiler
|
||||||
|
//#define SLIC3R_PROFILE_GUI
|
||||||
|
#if defined(SLIC3R_PROFILE) && defined(SLIC3R_PROFILE_GUI)
|
||||||
|
#include <Shiny/Shiny.h>
|
||||||
|
#define SLIC3R_GUI_PROFILE_FUNC() PROFILE_FUNC()
|
||||||
|
#define SLIC3R_GUI_PROFILE_BLOCK(name) PROFILE_BLOCK(name)
|
||||||
|
#define SLIC3R_GUI_PROFILE_UPDATE() PROFILE_UPDATE()
|
||||||
|
#define SLIC3R_GUI_PROFILE_OUTPUT(x) PROFILE_OUTPUT(x)
|
||||||
|
#else
|
||||||
|
#define SLIC3R_GUI_PROFILE_FUNC()
|
||||||
|
#define SLIC3R_GUI_PROFILE_BLOCK(name)
|
||||||
|
#define SLIC3R_GUI_PROFILE_UPDATE()
|
||||||
|
#define SLIC3R_GUI_PROFILE_OUTPUT(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // slic3r_GUI_Profile_hpp_
|
||||||
@@ -43,7 +43,6 @@
|
|||||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||||
#include "libslic3r/Exception.hpp"
|
#include "libslic3r/Exception.hpp"
|
||||||
#include "libslic3r/LifecycleEvents.hpp"
|
#include "libslic3r/LifecycleEvents.hpp"
|
||||||
#include "libslic3r/Utils.hpp"
|
|
||||||
|
|
||||||
#include "test_helpers.hpp"
|
#include "test_helpers.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
@@ -157,81 +156,6 @@ SCENARIO("Changing the number of solid shell layers does not make all surfaces i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Two copies of one object share a single slice. When a copy that has sliced itself becomes identical to an
|
|
||||||
// earlier object, it keeps its own layers while that object slices and only then takes the shared layers over.
|
|
||||||
TEST_CASE("An object that starts sharing another object's slices keeps its own layers until it takes them over", "[Print][Regression]")
|
|
||||||
{
|
|
||||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
||||||
config.set_deserialize_strict({
|
|
||||||
{ "layer_height", 0.2 },
|
|
||||||
{ "initial_layer_print_height", 0.2 },
|
|
||||||
{ "raft_layers", 1 }, // each object gets support layers of its own
|
|
||||||
{ "sparse_infill_density", "20%" }, // object_a's override below differs from this
|
|
||||||
});
|
|
||||||
// Activates the slicing-pipeline hook installed for the second slice below.
|
|
||||||
config.set_key_value("slicing_pipeline_plugin", new ConfigOptionStrings({"probe"}));
|
|
||||||
|
|
||||||
Print print;
|
|
||||||
Model model;
|
|
||||||
init_print({cube(20)}, print, model, config);
|
|
||||||
ModelObject *object_a = model.objects.front();
|
|
||||||
// add_object() gives the copy extruder 1, and objects only share slices when their configs match.
|
|
||||||
object_a->config.set_key_value("extruder", new ConfigOptionInt(1));
|
|
||||||
ModelObject *object_b = model.add_object(*object_a); // shares object_a's mesh
|
|
||||||
for (ModelInstance *instance : object_b->instances)
|
|
||||||
instance->set_offset(instance->get_offset() + Vec3d(40., 0., 0.));
|
|
||||||
// A per-object override makes the objects differ, so each slices itself.
|
|
||||||
object_a->config.set_key_value("sparse_infill_density", new ConfigOptionPercent(40));
|
|
||||||
|
|
||||||
print.apply(model, config);
|
|
||||||
print.process();
|
|
||||||
REQUIRE(print.objects().size() == 2);
|
|
||||||
// The non-const accessors hand out the layer vectors themselves, which the checks below compare.
|
|
||||||
PrintObject *print_b = print.get_object(1);
|
|
||||||
REQUIRE(print_b->model_object()->id() == object_b->id()); // Print slices its own copy of the model
|
|
||||||
REQUIRE(print_b->get_shared_object() == nullptr);
|
|
||||||
const LayerPtrs own_layers = print_b->layers();
|
|
||||||
const SupportLayerPtrs own_support_layers = print_b->support_layers();
|
|
||||||
REQUIRE(!own_layers.empty());
|
|
||||||
REQUIRE(!own_support_layers.empty());
|
|
||||||
|
|
||||||
// Without the override the objects are identical again: print_a redoes the steps the setting invalidated,
|
|
||||||
// and print_b shares it.
|
|
||||||
object_a->config.erase("sparse_infill_density");
|
|
||||||
print.apply(model, config);
|
|
||||||
REQUIRE(print.objects().size() == 2);
|
|
||||||
PrintObject *print_a = print.get_object(0);
|
|
||||||
|
|
||||||
// Erasing the override invalidates print_a's posPrepareInfill and the steps after it. Its hook is the first one
|
|
||||||
// print_a fires, and it runs after print_b starts sharing but before print_b takes the shared layers over.
|
|
||||||
bool hook_fired = false;
|
|
||||||
bool shared_while_a_runs = false;
|
|
||||||
bool kept_layers = false;
|
|
||||||
bool kept_support_layers = false;
|
|
||||||
{
|
|
||||||
Print::set_slicing_pipeline_hook_fn([&](Print &, const PrintObject *object, SlicingPipelineStepPlugin) {
|
|
||||||
if (object != print_a || hook_fired)
|
|
||||||
return;
|
|
||||||
hook_fired = true;
|
|
||||||
shared_while_a_runs = print_b->get_shared_object() == print_a;
|
|
||||||
kept_layers = print_b->layers() == own_layers;
|
|
||||||
kept_support_layers = print_b->support_layers() == own_support_layers;
|
|
||||||
});
|
|
||||||
const ScopeGuard reset_hook([] { Print::set_slicing_pipeline_hook_fn(nullptr); });
|
|
||||||
print.process();
|
|
||||||
}
|
|
||||||
|
|
||||||
REQUIRE(hook_fired);
|
|
||||||
CHECK(print.objects()[1] == print_b);
|
|
||||||
CHECK(shared_while_a_runs);
|
|
||||||
CHECK(kept_layers);
|
|
||||||
CHECK(kept_support_layers);
|
|
||||||
CHECK(print_b->get_shared_object() == print_a);
|
|
||||||
REQUIRE(!print_a->layers().empty());
|
|
||||||
CHECK(print_b->layers() == print_a->layers());
|
|
||||||
CHECK(print_b->support_layers() == print_a->support_layers());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Print::validate() warning collection
|
// Print::validate() warning collection
|
||||||
//
|
//
|
||||||
|
|||||||
+13
-215
@@ -28,7 +28,6 @@
|
|||||||
#include <miniz.h>
|
#include <miniz.h>
|
||||||
#include "libslic3r/TriangleMesh.hpp"
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
#include "libslic3r/TriangleSelector.hpp"
|
#include "libslic3r/TriangleSelector.hpp"
|
||||||
#include "libslic3r/TextureDisplacement.hpp"
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <ios>
|
#include <ios>
|
||||||
@@ -39,7 +38,6 @@
|
|||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <initializer_list>
|
|
||||||
|
|
||||||
#include <catch2/catch_tostring.hpp>
|
#include <catch2/catch_tostring.hpp>
|
||||||
#include <Eigen/Core>
|
#include <Eigen/Core>
|
||||||
@@ -351,24 +349,6 @@ static bool replace_in_3mf_entry(const std::string& path, const std::string& suf
|
|||||||
return replaced;
|
return replaced;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stores `model` as a one-plate project.
|
|
||||||
static void store_project(Model& model, const std::string& path, SaveStrategy strategy)
|
|
||||||
{
|
|
||||||
ScopedTemporaryDir backup_dir("orca_paint_src");
|
|
||||||
model.set_backup_path(backup_dir.string());
|
|
||||||
|
|
||||||
DynamicPrintConfig cfg;
|
|
||||||
PlateData plate;
|
|
||||||
plate.plate_index = 0;
|
|
||||||
StoreParams sp;
|
|
||||||
sp.path = path.c_str();
|
|
||||||
sp.model = &model;
|
|
||||||
sp.config = &cfg;
|
|
||||||
sp.strategy = strategy;
|
|
||||||
sp.plate_data_list.push_back(&plate);
|
|
||||||
REQUIRE(store_bbs_3mf(sp));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stores a one-plate project holding a cube whose first two facets are painted Extruder2 and
|
// Stores a one-plate project holding a cube whose first two facets are painted Extruder2 and
|
||||||
// Extruder3, which the exporter writes as paint_color="8" and paint_color="0C".
|
// Extruder3, which the exporter writes as paint_color="8" and paint_color="0C".
|
||||||
static void store_painted_cube(const std::string& path)
|
static void store_painted_cube(const std::string& path)
|
||||||
@@ -383,7 +363,19 @@ static void store_painted_cube(const std::string& path)
|
|||||||
selector.set_facet(1, EnforcerBlockerType::Extruder3);
|
selector.set_facet(1, EnforcerBlockerType::Extruder3);
|
||||||
REQUIRE(volume->mmu_segmentation_facets.set(selector));
|
REQUIRE(volume->mmu_segmentation_facets.set(selector));
|
||||||
}
|
}
|
||||||
store_project(model, path, SaveStrategy::Zip64 | SaveStrategy::Silence);
|
ScopedTemporaryDir backup_dir("orca_paint_src");
|
||||||
|
model.set_backup_path(backup_dir.string());
|
||||||
|
|
||||||
|
DynamicPrintConfig cfg;
|
||||||
|
PlateData plate;
|
||||||
|
plate.plate_index = 0;
|
||||||
|
StoreParams sp;
|
||||||
|
sp.path = path.c_str();
|
||||||
|
sp.model = &model;
|
||||||
|
sp.config = &cfg;
|
||||||
|
sp.strategy = SaveStrategy::Zip64 | SaveStrategy::Silence;
|
||||||
|
sp.plate_data_list.push_back(&plate);
|
||||||
|
REQUIRE(store_bbs_3mf(sp));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loads `path` through the BBS importer into `model`, releasing the plates it returns. The
|
// Loads `path` through the BBS importer into `model`, releasing the plates it returns. The
|
||||||
@@ -509,163 +501,6 @@ TEST_CASE("A project with malformed paint data loads without the damaged facet",
|
|||||||
REQUIRE(selector.num_facets(EnforcerBlockerType::Extruder3) == 1);
|
REQUIRE(selector.num_facets(EnforcerBlockerType::Extruder3) == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paints `triangles` of `volume` on texture displacement layer slot `slot`.
|
|
||||||
static void paint_texture_slot(ModelVolume& volume, int slot, std::initializer_list<int> triangles)
|
|
||||||
{
|
|
||||||
TriangleSelector selector(volume.mesh());
|
|
||||||
for (const int triangle : triangles)
|
|
||||||
selector.set_facet(triangle, EnforcerBlockerType::ENFORCER);
|
|
||||||
REQUIRE(volume.texture_displacement_facet(slot).set(selector));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Every triangle's texture paint on every slot, in the form the exporter writes it, so two volumes
|
|
||||||
// compare equal only when the same triangles are painted on the same slots.
|
|
||||||
static std::vector<std::string> texture_paint(const ModelVolume& volume)
|
|
||||||
{
|
|
||||||
std::vector<std::string> paint;
|
|
||||||
for (int slot = 0; slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++slot)
|
|
||||||
for (size_t i = 0; i < volume.mesh().its.indices.size(); ++i)
|
|
||||||
paint.push_back(volume.texture_displacement_facet(slot).get_triangle_as_string(int(i)));
|
|
||||||
return paint;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A cube with one texture displacement layer in slot 2 and texture paint on gapped triangles of two
|
|
||||||
// slots. The layer's image bytes need not decode: the archive stores them as they are.
|
|
||||||
static ModelVolume& add_texture_displaced_cube(Model& model)
|
|
||||||
{
|
|
||||||
ModelObject* object = model.add_object();
|
|
||||||
ModelVolume* volume = object->add_volume(make_cube(10., 10., 10.));
|
|
||||||
object->add_instance();
|
|
||||||
|
|
||||||
TextureDisplacementLayer layer;
|
|
||||||
layer.slot = 2;
|
|
||||||
layer.name = "Bark";
|
|
||||||
layer.depth_mm = 1.25f;
|
|
||||||
layer.image_data = std::make_shared<std::vector<unsigned char>>(std::vector<unsigned char>{ 0x89, 'P', 'N', 'G', 1, 2, 3 });
|
|
||||||
volume->texture_displacement_layers.push_back(layer);
|
|
||||||
volume->texture_displacement_options.active_slot = 2;
|
|
||||||
|
|
||||||
paint_texture_slot(*volume, 2, { 3, 7 });
|
|
||||||
paint_texture_slot(*volume, 5, { 10 });
|
|
||||||
return *volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Texture displacement layers, textures and paint survive a project round trip", "[3mf]")
|
|
||||||
{
|
|
||||||
// A single model file, and one file per object (read by a separate importer).
|
|
||||||
const SaveStrategy strategy = GENERATE(SaveStrategy::Zip64 | SaveStrategy::Silence,
|
|
||||||
SaveStrategy::Zip64 | SaveStrategy::Silence | SaveStrategy::SplitModel);
|
|
||||||
INFO("save strategy " << int(strategy));
|
|
||||||
|
|
||||||
ScopedTemporaryFile temp(".3mf");
|
|
||||||
Model source;
|
|
||||||
const ModelVolume& painted = add_texture_displaced_cube(source);
|
|
||||||
const std::vector<std::string> paint = texture_paint(painted);
|
|
||||||
REQUIRE(std::count_if(paint.begin(), paint.end(), [](const std::string& s) { return !s.empty(); }) == 3);
|
|
||||||
store_project(source, temp.string(), strategy);
|
|
||||||
|
|
||||||
ScopedTemporaryDir backup_dir("orca_texdisp_dst");
|
|
||||||
Model model;
|
|
||||||
REQUIRE(load_project(temp.string(), model, backup_dir));
|
|
||||||
REQUIRE(model.objects.size() == 1);
|
|
||||||
const ModelVolume& volume = *model.objects.front()->volumes.front();
|
|
||||||
|
|
||||||
REQUIRE(volume.texture_displacement_layers.size() == 1);
|
|
||||||
const TextureDisplacementLayer& layer = volume.texture_displacement_layers.front();
|
|
||||||
CHECK(layer.slot == 2);
|
|
||||||
CHECK(layer.name == "Bark");
|
|
||||||
CHECK_THAT(layer.depth_mm, Catch::Matchers::WithinAbs(1.25f, 1e-6f));
|
|
||||||
REQUIRE(layer.image_data);
|
|
||||||
CHECK(*layer.image_data == *painted.texture_displacement_layers.front().image_data);
|
|
||||||
CHECK(volume.texture_displacement_options.active_slot == 2);
|
|
||||||
CHECK(texture_paint(volume) == paint);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Volumes sharing a mesh keep their own texture displacement paint", "[3mf]")
|
|
||||||
{
|
|
||||||
Model source;
|
|
||||||
ModelObject* first_object = source.add_object();
|
|
||||||
ModelVolume* first = first_object->add_volume(make_cube(10., 10., 10.));
|
|
||||||
first_object->add_instance();
|
|
||||||
ModelVolume* second = source.add_object(*first_object)->volumes.front();
|
|
||||||
REQUIRE(second->mesh_ptr() == first->mesh_ptr());
|
|
||||||
paint_texture_slot(*first, 0, { 3 });
|
|
||||||
paint_texture_slot(*second, 0, { 7 });
|
|
||||||
|
|
||||||
// What a project save from the app uses: one file per object, an identical mesh written once.
|
|
||||||
ScopedTemporaryFile temp(".3mf");
|
|
||||||
store_project(source, temp.string(),
|
|
||||||
SaveStrategy::Zip64 | SaveStrategy::Silence | SaveStrategy::SplitModel | SaveStrategy::ShareMesh);
|
|
||||||
|
|
||||||
ScopedTemporaryDir backup_dir("orca_texdisp_dst");
|
|
||||||
Model model;
|
|
||||||
REQUIRE(load_project(temp.string(), model, backup_dir));
|
|
||||||
REQUIRE(model.objects.size() == 2);
|
|
||||||
CHECK(texture_paint(*model.objects[0]->volumes.front()) == texture_paint(*first));
|
|
||||||
CHECK(texture_paint(*model.objects[1]->volumes.front()) == texture_paint(*second));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("A texture displacement layer never loads its image from outside the project", "[3mf]")
|
|
||||||
{
|
|
||||||
// A file the layer names, on the machine opening the project, that the project did not carry.
|
|
||||||
ScopedTemporaryFile texture(".png");
|
|
||||||
{
|
|
||||||
boost::nowide::ofstream file(texture.string(), std::ios::binary);
|
|
||||||
file << "not part of the project";
|
|
||||||
}
|
|
||||||
REQUIRE(boost::filesystem::file_size(texture.path()) > 0);
|
|
||||||
|
|
||||||
Model source;
|
|
||||||
ModelObject* object = source.add_object();
|
|
||||||
ModelVolume* volume = object->add_volume(make_cube(10., 10., 10.));
|
|
||||||
object->add_instance();
|
|
||||||
TextureDisplacementLayer layer;
|
|
||||||
layer.slot = 0;
|
|
||||||
layer.name = "Elsewhere";
|
|
||||||
layer.path = texture.string();
|
|
||||||
layer.depth_mm = 0.75f;
|
|
||||||
volume->texture_displacement_layers.push_back(layer);
|
|
||||||
|
|
||||||
ScopedTemporaryFile temp(".3mf");
|
|
||||||
store_project(source, temp.string(), SaveStrategy::Zip64 | SaveStrategy::Silence);
|
|
||||||
|
|
||||||
ScopedTemporaryDir backup_dir("orca_texdisp_dst");
|
|
||||||
Model model;
|
|
||||||
REQUIRE(load_project(temp.string(), model, backup_dir));
|
|
||||||
REQUIRE(model.objects.size() == 1);
|
|
||||||
const std::vector<TextureDisplacementLayer>& layers = model.objects.front()->volumes.front()->texture_displacement_layers;
|
|
||||||
REQUIRE(layers.size() == 1);
|
|
||||||
CHECK(layers.front().name == "Elsewhere");
|
|
||||||
CHECK_THAT(layers.front().depth_mm, Catch::Matchers::WithinAbs(0.75f, 1e-6f));
|
|
||||||
CHECK_FALSE(layers.front().image_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("A project with a malformed texture displacement value still loads", "[3mf][Regression]")
|
|
||||||
{
|
|
||||||
const auto [from, to] = GENERATE(table<std::string, std::string>({
|
|
||||||
{ "\"islands\":[]", "\"islands\":[[null,0,0,1]]" }, // a number that is null
|
|
||||||
{ "\"slot\":2", "\"slot\":8" }, // a slot past the last
|
|
||||||
}));
|
|
||||||
INFO(from << " -> " << to);
|
|
||||||
|
|
||||||
ScopedTemporaryFile temp(".3mf");
|
|
||||||
Model source;
|
|
||||||
const ModelVolume& painted = add_texture_displaced_cube(source);
|
|
||||||
store_project(source, temp.string(), SaveStrategy::Zip64 | SaveStrategy::Silence);
|
|
||||||
REQUIRE(replace_in_3mf_entry(temp.string(), ".json", from, to));
|
|
||||||
|
|
||||||
ScopedTemporaryDir backup_dir("orca_texdisp_dst");
|
|
||||||
Model model;
|
|
||||||
bool loaded = false;
|
|
||||||
REQUIRE_NOTHROW(loaded = load_project(temp.string(), model, backup_dir));
|
|
||||||
REQUIRE(loaded);
|
|
||||||
REQUIRE(model.objects.size() == 1);
|
|
||||||
const ModelVolume& volume = *model.objects.front()->volumes.front();
|
|
||||||
// The layer stack is dropped as a whole; the paint, stored apart from it, still loads.
|
|
||||||
CHECK(volume.texture_displacement_layers.empty());
|
|
||||||
CHECK(texture_paint(volume) == texture_paint(painted));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The recipe lives only in the BBS-native backend, because that is the only one that runs:
|
// The recipe lives only in the BBS-native backend, because that is the only one that runs:
|
||||||
// store_bbs_3mf is the sole exporter the app calls, and 3mf.cpp's load_3mf is reached only for
|
// store_bbs_3mf is the sole exporter the app calls, and 3mf.cpp's load_3mf is reached only for
|
||||||
// files fingerprinted as PrusaSlicer's, which never carry a recipe. This locks in both halves:
|
// files fingerprinted as PrusaSlicer's, which never carry a recipe. This locks in both halves:
|
||||||
@@ -2208,40 +2043,3 @@ TEST_CASE("3MF XML entries declaring more than an int can hold fail to load", "[
|
|||||||
CHECK_FALSE(parser.check_3mf_from_prusa(path));
|
CHECK_FALSE(parser.check_3mf_from_prusa(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Loading a gcode.3mf from a stream keeps the plate's pick and pattern file paths", "[3mf][Regression]")
|
|
||||||
{
|
|
||||||
Model model;
|
|
||||||
REQUIRE(load_stl((std::string(TEST_DATA_DIR) + "/test_3mf/Prusa.stl").c_str(), &model));
|
|
||||||
model.add_default_instances();
|
|
||||||
ScopedTemporaryDir backup_dir("orca_pick");
|
|
||||||
model.set_backup_path(backup_dir.string());
|
|
||||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
|
||||||
|
|
||||||
auto plate = std::make_unique<PlateData>();
|
|
||||||
plate->plate_index = 0;
|
|
||||||
plate->pick_file = (backup_dir.path() / "pick.png").string();
|
|
||||||
plate->pattern_bbox_file = (backup_dir.path() / "plate.json").string();
|
|
||||||
|
|
||||||
ScopedTemporaryFile temp(".3mf");
|
|
||||||
const std::string test_file = temp.string();
|
|
||||||
StoreParams store_params;
|
|
||||||
store_params.path = test_file.c_str();
|
|
||||||
store_params.model = &model;
|
|
||||||
store_params.config = &config;
|
|
||||||
store_params.plate_data_list.push_back(plate.get());
|
|
||||||
store_params.strategy = SaveStrategy::Zip64 | SaveStrategy::Silence;
|
|
||||||
REQUIRE(store_bbs_3mf(store_params));
|
|
||||||
|
|
||||||
boost::nowide::ifstream in(test_file, std::ios::binary);
|
|
||||||
Model dst_model;
|
|
||||||
DynamicPrintConfig dst_config;
|
|
||||||
PlateDataPtrs dst_plates;
|
|
||||||
Semver file_version;
|
|
||||||
dst_model.set_backup_path(backup_dir.string());
|
|
||||||
REQUIRE(load_gcode_3mf_from_stream(in, &dst_config, &dst_model, &dst_plates, &file_version));
|
|
||||||
REQUIRE(dst_plates.size() == 1);
|
|
||||||
CHECK(dst_plates.front()->pick_file == "Metadata/pick_1.png");
|
|
||||||
CHECK(dst_plates.front()->pattern_bbox_file == "Metadata/plate_1.json");
|
|
||||||
release_PlateData_list(dst_plates);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
|
||||||
#include <boost/filesystem.hpp>
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
#include "libslic3r/AABBMesh.hpp"
|
#include "libslic3r/AABBMesh.hpp"
|
||||||
@@ -2158,177 +2157,6 @@ TEST_CASE("A second bake beside a first comes out as fine as a single bake", "[T
|
|||||||
CHECK(second <= single * 5 / 4);
|
CHECK(second <= single * 5 / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The .3mf stores the layer stack as JSON (see texture_displacement_layers_to_json). Two properties
|
|
||||||
// matter: everything the UV editor and the panel can set has to survive a round trip, and a document
|
|
||||||
// written by a build that knew fewer fields has to keep loading, with the missing ones left at their
|
|
||||||
// defaults rather than zeroed.
|
|
||||||
TEST_CASE("Texture displacement layers survive a JSON round trip", "[TextureDisplacement]")
|
|
||||||
{
|
|
||||||
std::vector<TextureDisplacementLayer> layers(2);
|
|
||||||
TextureDisplacementLayer &a = layers[0];
|
|
||||||
a.slot = 0;
|
|
||||||
a.name = "Bark";
|
|
||||||
a.path = "/textures/bark.png";
|
|
||||||
a.depth_mm = 1.25f;
|
|
||||||
a.tiling_scale = 7.5f;
|
|
||||||
a.rotation_deg = 30.f;
|
|
||||||
a.offset = Vec2f(0.25f, -0.5f);
|
|
||||||
a.invert = true;
|
|
||||||
a.midlevel = 0.125f;
|
|
||||||
a.smoothing = 0.75f;
|
|
||||||
a.edge_smoothing = true;
|
|
||||||
a.edge_smoothing_amount = 0.25f;
|
|
||||||
a.auto_connect_islands = false;
|
|
||||||
a.tile_enabled = false;
|
|
||||||
a.tile_method = TextureTileMethod::MirroredRepeat;
|
|
||||||
a.projection_method = TextureProjectionMethod::LSCM;
|
|
||||||
a.blend_mode = TextureBlendMode::Subtract;
|
|
||||||
a.color_enabled = true;
|
|
||||||
a.lscm_seam_angle_deg = 45.f;
|
|
||||||
a.island_padding_mm = 0.5f;
|
|
||||||
a.lscm_seam_edges = { { 1, 2 }, { 3, 5 } };
|
|
||||||
a.islands = { TextureIsland{ Vec2f(1.f, 2.f), 15.f, 1.5f }, TextureIsland{} };
|
|
||||||
a.island_groups = { 0, 0 };
|
|
||||||
a.lscm_uv_overrides = { { -4, Vec2f(0.5f, 0.75f) } };
|
|
||||||
|
|
||||||
TextureDisplacementLayer &b = layers[1];
|
|
||||||
b.slot = 1;
|
|
||||||
b.projection_method = TextureProjectionMethod::ViewProjected;
|
|
||||||
b.view_project_right = Vec3f(0.f, 1.f, 0.f);
|
|
||||||
b.view_project_up = Vec3f(0.f, 0.f, 1.f);
|
|
||||||
b.view_project_projective = true;
|
|
||||||
for (size_t i = 0; i < b.view_project_matrix.size(); ++i)
|
|
||||||
b.view_project_matrix[i] = float(i) + 0.5f;
|
|
||||||
|
|
||||||
TextureDisplacementOptions options;
|
|
||||||
options.displace_border = false;
|
|
||||||
options.smooth_enabled = true;
|
|
||||||
options.smooth_strength = 0.6f;
|
|
||||||
options.smooth_iterations = 5;
|
|
||||||
options.pipeline_v2 = false;
|
|
||||||
options.v2_max_triangles_k = 250;
|
|
||||||
options.color_mix_count = 3;
|
|
||||||
options.color_despeckle = 4;
|
|
||||||
options.active_slot = 3;
|
|
||||||
options.panel_mode = 1;
|
|
||||||
|
|
||||||
const std::string json = texture_displacement_layers_to_json(layers, options);
|
|
||||||
|
|
||||||
std::vector<TextureDisplacementLayer> read_layers;
|
|
||||||
TextureDisplacementOptions read_options;
|
|
||||||
REQUIRE(texture_displacement_layers_from_json(json, read_layers, read_options));
|
|
||||||
REQUIRE(read_layers.size() == layers.size());
|
|
||||||
|
|
||||||
const TextureDisplacementLayer &ra = read_layers[0];
|
|
||||||
CHECK(ra.name == a.name);
|
|
||||||
CHECK(ra.path == a.path);
|
|
||||||
CHECK_THAT(ra.depth_mm, WithinAbs(a.depth_mm, 1e-6f));
|
|
||||||
CHECK_THAT(ra.tiling_scale, WithinAbs(a.tiling_scale, 1e-6f));
|
|
||||||
CHECK_THAT(ra.rotation_deg, WithinAbs(a.rotation_deg, 1e-6f));
|
|
||||||
CHECK(ra.offset.isApprox(a.offset));
|
|
||||||
CHECK(ra.invert == a.invert);
|
|
||||||
CHECK_THAT(ra.midlevel, WithinAbs(a.midlevel, 1e-6f));
|
|
||||||
CHECK_THAT(ra.smoothing, WithinAbs(a.smoothing, 1e-6f));
|
|
||||||
CHECK(ra.edge_smoothing == a.edge_smoothing);
|
|
||||||
CHECK_THAT(ra.edge_smoothing_amount, WithinAbs(a.edge_smoothing_amount, 1e-6f));
|
|
||||||
CHECK(ra.auto_connect_islands == a.auto_connect_islands);
|
|
||||||
CHECK(ra.tile_enabled == a.tile_enabled);
|
|
||||||
CHECK(ra.tile_method == a.tile_method);
|
|
||||||
CHECK(ra.projection_method == a.projection_method);
|
|
||||||
CHECK(ra.blend_mode == a.blend_mode);
|
|
||||||
CHECK(ra.color_enabled == a.color_enabled);
|
|
||||||
CHECK_THAT(ra.lscm_seam_angle_deg, WithinAbs(a.lscm_seam_angle_deg, 1e-6f));
|
|
||||||
CHECK_THAT(ra.island_padding_mm, WithinAbs(a.island_padding_mm, 1e-6f));
|
|
||||||
CHECK(ra.lscm_seam_edges == a.lscm_seam_edges);
|
|
||||||
REQUIRE(ra.islands.size() == a.islands.size());
|
|
||||||
CHECK(ra.islands[0].offset.isApprox(a.islands[0].offset));
|
|
||||||
CHECK_THAT(ra.islands[0].rotation_deg, WithinAbs(a.islands[0].rotation_deg, 1e-6f));
|
|
||||||
CHECK_THAT(ra.islands[0].scale, WithinAbs(a.islands[0].scale, 1e-6f));
|
|
||||||
CHECK(ra.island_groups == a.island_groups);
|
|
||||||
REQUIRE(ra.lscm_uv_overrides.size() == 1);
|
|
||||||
CHECK(ra.lscm_uv_overrides[0].first == -4);
|
|
||||||
CHECK(ra.lscm_uv_overrides[0].second.isApprox(Vec2f(0.5f, 0.75f)));
|
|
||||||
|
|
||||||
const TextureDisplacementLayer &rb = read_layers[1];
|
|
||||||
CHECK(rb.projection_method == TextureProjectionMethod::ViewProjected);
|
|
||||||
CHECK(rb.view_project_right.isApprox(b.view_project_right));
|
|
||||||
CHECK(rb.view_project_up.isApprox(b.view_project_up));
|
|
||||||
CHECK(rb.view_project_projective);
|
|
||||||
CHECK(rb.view_project_matrix == b.view_project_matrix);
|
|
||||||
|
|
||||||
CHECK(read_options.displace_border == options.displace_border);
|
|
||||||
CHECK(read_options.smooth_enabled == options.smooth_enabled);
|
|
||||||
CHECK_THAT(read_options.smooth_strength, WithinAbs(options.smooth_strength, 1e-6f));
|
|
||||||
CHECK(read_options.smooth_iterations == options.smooth_iterations);
|
|
||||||
CHECK(read_options.pipeline_v2 == options.pipeline_v2);
|
|
||||||
CHECK(read_options.v2_max_triangles_k == options.v2_max_triangles_k);
|
|
||||||
CHECK(read_options.color_mix_count == options.color_mix_count);
|
|
||||||
CHECK(read_options.color_despeckle == options.color_despeckle);
|
|
||||||
// Where the panel was left comes back with the rest, so a reopened project is on the same layer.
|
|
||||||
CHECK(read_options.active_slot == options.active_slot);
|
|
||||||
CHECK(read_options.panel_mode == options.panel_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Texture displacement JSON keeps defaults for keys it does not carry", "[TextureDisplacement]")
|
|
||||||
{
|
|
||||||
// What a build that knew fewer fields would have written: one layer, almost nothing set.
|
|
||||||
const std::string sparse = R"({"version":1,"layers":[{"slot":2,"name":"Old","depth_mm":0.75}]})";
|
|
||||||
|
|
||||||
std::vector<TextureDisplacementLayer> layers;
|
|
||||||
TextureDisplacementOptions options;
|
|
||||||
REQUIRE(texture_displacement_layers_from_json(sparse, layers, options));
|
|
||||||
REQUIRE(layers.size() == 1);
|
|
||||||
|
|
||||||
const TextureDisplacementLayer &l = layers[0];
|
|
||||||
const TextureDisplacementLayer fresh;
|
|
||||||
const TextureDisplacementOptions defaults;
|
|
||||||
CHECK(l.slot == 2);
|
|
||||||
CHECK(l.name == "Old");
|
|
||||||
CHECK_THAT(l.depth_mm, WithinAbs(0.75f, 1e-6f));
|
|
||||||
// Everything absent keeps the struct's own default rather than becoming zero.
|
|
||||||
CHECK_THAT(l.tiling_scale, WithinAbs(fresh.tiling_scale, 1e-6f));
|
|
||||||
CHECK(l.auto_connect_islands == fresh.auto_connect_islands);
|
|
||||||
CHECK(l.tile_enabled == fresh.tile_enabled);
|
|
||||||
CHECK(l.projection_method == fresh.projection_method);
|
|
||||||
CHECK_THAT(l.lscm_seam_angle_deg, WithinAbs(fresh.lscm_seam_angle_deg, 1e-6f));
|
|
||||||
CHECK(options.color_mix_count == defaults.color_mix_count);
|
|
||||||
CHECK(options.pipeline_v2 == defaults.pipeline_v2);
|
|
||||||
CHECK(options.active_slot == defaults.active_slot);
|
|
||||||
CHECK(options.panel_mode == defaults.panel_mode);
|
|
||||||
|
|
||||||
std::vector<TextureDisplacementLayer> unused_layers;
|
|
||||||
TextureDisplacementOptions unused_options;
|
|
||||||
CHECK(!texture_displacement_layers_from_json("not json at all", unused_layers, unused_options));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Texture displacement JSON with a wrong-typed element or an out-of-range slot is rejected", "[TextureDisplacement]")
|
|
||||||
{
|
|
||||||
const std::string layer = GENERATE(as<std::string>{},
|
|
||||||
R"({"slot":0,"offset":["x",0]})",
|
|
||||||
R"({"slot":0,"view_project_right":[null,0,0]})",
|
|
||||||
R"({"slot":0,"view_project_matrix":[0,0,0,0,0,0,0,0,0,0,0,"x"]})",
|
|
||||||
R"({"slot":0,"lscm_seam_edges":[1,null]})",
|
|
||||||
R"({"slot":0,"islands":[[0,0,"x",1]]})",
|
|
||||||
R"({"slot":0,"lscm_uv_overrides":[[null,0,0]]})",
|
|
||||||
R"({"slot":8})",
|
|
||||||
R"({"slot":-1})",
|
|
||||||
R"({"slot":"x"})",
|
|
||||||
R"({"name":"No slot"})");
|
|
||||||
INFO(layer);
|
|
||||||
|
|
||||||
std::vector<TextureDisplacementLayer> layers(1);
|
|
||||||
layers.front().name = "Kept";
|
|
||||||
TextureDisplacementOptions options;
|
|
||||||
options.color_mix_count = 7;
|
|
||||||
|
|
||||||
bool parsed = true;
|
|
||||||
REQUIRE_NOTHROW(parsed = texture_displacement_layers_from_json(R"({"version":1,"layers":[)" + layer + "]}", layers, options));
|
|
||||||
CHECK_FALSE(parsed);
|
|
||||||
REQUIRE(layers.size() == 1);
|
|
||||||
CHECK(layers.front().name == "Kept");
|
|
||||||
CHECK(options.color_mix_count == 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("whether a layer's texture has colour agrees with its decode", "[TextureDisplacement]")
|
TEST_CASE("whether a layer's texture has colour agrees with its decode", "[TextureDisplacement]")
|
||||||
{
|
{
|
||||||
TextureDisplacementLayer gray;
|
TextureDisplacementLayer gray;
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_plugin_json_depth.cpp
|
test_plugin_json_depth.cpp
|
||||||
test_shortcuts.cpp
|
test_shortcuts.cpp
|
||||||
test_texture_color_palette.cpp
|
test_texture_color_palette.cpp
|
||||||
test_texture_island_view.cpp
|
|
||||||
test_file_url.cpp
|
test_file_url.cpp
|
||||||
test_user_manager.cpp
|
test_user_manager.cpp
|
||||||
../fff_print/test_helpers.cpp
|
../fff_print/test_helpers.cpp
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
// How the texture displacement gizmo's view follows the UV editor's island highlight. The rules live in
|
|
||||||
// libslic3r_gui; this is the suite that links it. Same Windows include prologue as
|
|
||||||
// test_texture_color_palette.cpp (wx pulls in <windows.h>; keep WIN32_LEAN_AND_MEAN / NOMINMAX ahead of it).
|
|
||||||
#ifdef WIN32
|
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#endif
|
|
||||||
#ifndef NOMINMAX
|
|
||||||
#define NOMINMAX
|
|
||||||
#endif
|
|
||||||
#include <Windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
#include <catch2/generators/catch_generators.hpp>
|
|
||||||
|
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp"
|
|
||||||
|
|
||||||
using Follow = Slic3r::GUI::GLGizmoTextureDisplacement::IslandViewFollow;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// The View row's indices, and step()'s "apply nothing".
|
|
||||||
constexpr int NORMAL = 0, FAST = 1, CHECKER = 2, DISTORTION = 3, NONE = -1;
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("a Normal view gives way to Fast while islands are selected and comes back once none are", "[TextureIslandView][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
Follow f;
|
|
||||||
CHECK(f.step(false, NORMAL, false) == NONE);
|
|
||||||
CHECK(f.step(true, NORMAL, false) == FAST);
|
|
||||||
CHECK(f.step(true, FAST, false) == NONE);
|
|
||||||
CHECK(f.step(false, FAST, false) == NORMAL);
|
|
||||||
CHECK(f.step(false, NORMAL, false) == NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("a Fast, Checker or Distortion view is left alone by the island highlight", "[TextureIslandView][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
const int view = GENERATE(FAST, CHECKER, DISTORTION);
|
|
||||||
Follow f;
|
|
||||||
CHECK(f.step(true, view, false) == NONE);
|
|
||||||
CHECK(f.step(false, view, false) == NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("arriving in Normal with islands already selected switches to Fast", "[TextureIslandView][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
// The pane's Height button over Checker lands in Normal with the islands still selected.
|
|
||||||
Follow f;
|
|
||||||
CHECK(f.step(true, CHECKER, false) == NONE);
|
|
||||||
CHECK(f.step(true, NORMAL, false) == FAST);
|
|
||||||
CHECK(f.step(false, FAST, false) == NORMAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Normal picked while islands are selected stays until they are deselected", "[TextureIslandView][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
Follow f;
|
|
||||||
REQUIRE(f.step(true, NORMAL, false) == FAST);
|
|
||||||
f.picked(NORMAL);
|
|
||||||
CHECK(f.step(true, NORMAL, false) == NONE);
|
|
||||||
// A debug stage opening and closing over it changes nothing.
|
|
||||||
CHECK(f.step(true, NORMAL, true) == NONE);
|
|
||||||
CHECK(f.step(true, NORMAL, false) == NONE);
|
|
||||||
CHECK(f.step(false, NORMAL, false) == NONE);
|
|
||||||
CHECK(f.step(true, NORMAL, false) == FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Fast picked while it was switched on stays after deselecting", "[TextureIslandView][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
Follow f;
|
|
||||||
REQUIRE(f.step(true, NORMAL, false) == FAST);
|
|
||||||
f.picked(FAST);
|
|
||||||
CHECK(f.step(false, FAST, false) == NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Normal picked with nothing selected does not hold off the next switch", "[TextureIslandView][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
Follow f;
|
|
||||||
CHECK(f.step(false, NORMAL, false) == NONE);
|
|
||||||
f.picked(NORMAL);
|
|
||||||
CHECK(f.step(true, NORMAL, false) == FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("a debug stage holds Normal while it is open, and the switch follows once it closes", "[TextureIslandView][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
Follow f;
|
|
||||||
REQUIRE(f.step(true, NORMAL, false) == FAST);
|
|
||||||
// The stage takes Normal itself, with the islands still selected.
|
|
||||||
CHECK(f.step(true, NORMAL, true) == NONE);
|
|
||||||
CHECK(f.step(true, NORMAL, false) == FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("islands deselected while a debug stage is open apply no view over it", "[TextureIslandView][TextureDisplacement]")
|
|
||||||
{
|
|
||||||
Follow f;
|
|
||||||
REQUIRE(f.step(true, NORMAL, false) == FAST);
|
|
||||||
CHECK(f.step(false, NORMAL, true) == NONE);
|
|
||||||
CHECK(f.step(false, NORMAL, false) == NONE);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user