mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-16 20:31:14 +00:00
Compare commits
40
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
638dd9f100 | ||
|
|
82aee1abdd | ||
|
|
cefdd9b609 | ||
|
|
caade80624 | ||
|
|
cbd8ce98d5 | ||
|
|
99714afc4c | ||
|
|
a72aa236c4 | ||
|
|
8baeba1341 | ||
|
|
e0a6000d50 | ||
|
|
3d07663ab2 | ||
|
|
29b6bbb8c9 | ||
|
|
8b6c074c69 | ||
|
|
109d09e0b8 | ||
|
|
b00237807d | ||
|
|
4149401c53 | ||
|
|
f6cf18fb96 | ||
|
|
4ee1b9c6e9 | ||
|
|
bc8d0a7858 | ||
|
|
e0651ec31d | ||
|
|
0caefcf4aa | ||
|
|
86905a8a20 | ||
|
|
d4fabbf7c8 | ||
|
|
51efb24752 | ||
|
|
cf9ec18df5 | ||
|
|
99de46b9d0 | ||
|
|
258f927a0f | ||
|
|
b8d40ed61f | ||
|
|
6aa613ccf0 | ||
|
|
54e6df0235 | ||
|
|
7599868797 | ||
|
|
81ec931990 | ||
|
|
8fd97b5120 | ||
|
|
aacc36b9d2 | ||
|
|
79ed246d26 | ||
|
|
92b5301bf6 | ||
|
|
20c157b0f4 | ||
|
|
4cf16febf6 | ||
|
|
70e52a105f | ||
|
|
b525cb79e1 | ||
|
|
86ab598899 |
@@ -569,12 +569,18 @@ on the **first** call. So:
|
||||
#endif
|
||||
```
|
||||
|
||||
**Before GTK starts**, only the environment exists. `CLI::run` (`src/OrcaSlicer.cpp`) branches on
|
||||
`GDK_BACKEND` (an `x11` prefix means the X11 opt-in). On the default path it forces `GDK_BACKEND=x11`
|
||||
when wx lacks EGL and `WAYLAND_DISPLAY` is set. It sets `WEBKIT_DISABLE_COMPOSITING_MODE=1`
|
||||
(non-replacing) only when both `DISPLAY` and `WAYLAND_DISPLAY` are set, and calls `XInitThreads()`
|
||||
**Before GTK starts**, only the environment exists. `CLI::run` (`src/OrcaSlicer.cpp`) sets the NVIDIA
|
||||
DMABUF workaround and then branches on `GDK_BACKEND` (an `x11` prefix means the X11 opt-in). On both
|
||||
paths, when `/proc/driver/nvidia/version` exists and the linked WebKitGTK honours the switch (>= 2.44,
|
||||
`WebView::SupportsForceSharedMemory()`), it sets `WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and
|
||||
`WEBKIT_FORCE_DMABUF_RENDERER=1` (non-replacing): the proprietary driver cannot back WebKit's hardware
|
||||
DMABUF renderer. On the default path it forces `GDK_BACKEND=x11` when wx lacks EGL and `WAYLAND_DISPLAY`
|
||||
is set, and sets `WEBKIT_DISABLE_COMPOSITING_MODE=1` (non-replacing) inside that branch when the linked
|
||||
WebKitGTK is older than 2.42: a forced X11 backend in a Wayland session is the only default-path case
|
||||
that runs through XWayland, which `DISPLAY` and `WAYLAND_DISPLAY` alone do not tell you (a native
|
||||
Wayland app inherits both). It calls `XInitThreads()`
|
||||
only when `DISPLAY` is set. With neither variable set, the GUI refuses to start ("Neither DISPLAY nor
|
||||
WAYLAND_DISPLAY set"). The details and the WebKit rule (c12912e0df) are in
|
||||
WAYLAND_DISPLAY set"). The details and the WebKit rules (c12912e0df) are in
|
||||
`references/webview-gl-aui-media.md` §WebKitGTK on Linux sessions.
|
||||
|
||||
**wx's own detectors**, for reference:
|
||||
|
||||
@@ -61,7 +61,8 @@ available; GTK2/WebKit1 is an opt-out configuration the GUI does not support.
|
||||
11. A web host that re-themes in place handles `EVT_WEBVIEW_RECREATED` without `Skip()`; one that
|
||||
needs a reload lets it through. §[Orca wrapper](#orca-webview-wrapper-widgetswebview)
|
||||
12. Keep `WebViewWebKit`'s destructor removing the `"wx"` handler. §[Orca wrapper](#orca-webview-wrapper-widgetswebview)
|
||||
13. Do not widen `WEBKIT_DISABLE_COMPOSITING_MODE` beyond Orca's default-path XWayland case, and never
|
||||
13. Do not widen `WEBKIT_DISABLE_COMPOSITING_MODE` beyond the no-EGL default path where Orca forces
|
||||
`GDK_BACKEND=x11`, for WebKitGTK older than 2.42, and never
|
||||
make a page's progress depend only on a C++→JS callback. §[WebKitGTK on Linux](#webkitgtk-on-linux-sessions)
|
||||
14. GL attribute lists: legacy `int[]` lists end with `0` and spell out `WX_GL_RGBA` and
|
||||
`WX_GL_DOUBLEBUFFER`; `wxGLAttributes`/`wxGLContextAttrs` end with `EndList()`; MSAA is requested
|
||||
@@ -567,33 +568,69 @@ vue-resize workaround script and sets a persistent SQLite cookie store on the vi
|
||||
## WebKitGTK on Linux sessions
|
||||
|
||||
**Mechanism.** `CLI::run` (`src/OrcaSlicer.cpp`, under `#ifdef __WXGTK__`, before GTK initialises)
|
||||
branches on `GDK_BACKEND`:
|
||||
sets the NVIDIA DMABUF workaround and then branches on `GDK_BACKEND`:
|
||||
- **Both paths**, before the split: when `/proc/driver/nvidia/version` exists and the linked WebKitGTK
|
||||
honours the switch (>= 2.44, `WebView::SupportsForceSharedMemory()`), it sets
|
||||
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=1` and `WEBKIT_FORCE_DMABUF_RENDERER=1` non-replacing. The
|
||||
proprietary driver cannot back WebKit's hardware DMABUF renderer, so the shared-memory transport keeps
|
||||
accelerated compositing instead of dropping it; `WEBKIT_FORCE_DMABUF_RENDERER` clears the
|
||||
Debian/Ubuntu/openSUSE `isNVIDIA()` early return, which otherwise returns before the FORCE_SHM check.
|
||||
- **Default path** (`GDK_BACKEND` unset or not starting with `x11`): if wx lacks EGL (`!wxHAS_EGL`) and
|
||||
`WAYLAND_DISPLAY` is set, it forces `GDK_BACKEND=x11`; it sets `WEBKIT_DISABLE_COMPOSITING_MODE=1`
|
||||
non-replacing **only** when both `DISPLAY` and `WAYLAND_DISPLAY` are non-empty (a Wayland session with
|
||||
XWayland available — the session, not the backend GTK will pick); `XInitThreads()` runs only when
|
||||
`DISPLAY` is set.
|
||||
`WAYLAND_DISPLAY` is set, it forces `GDK_BACKEND=x11`, and in that same branch it sets
|
||||
`WEBKIT_DISABLE_COMPOSITING_MODE=1` non-replacing when the linked WebKitGTK predates 2.42
|
||||
(`WebView::NeedsCompositingWorkaround()`). That forced X11 backend in a Wayland session is the only
|
||||
default-path case that really runs through XWayland; `DISPLAY` and `WAYLAND_DISPLAY` alone do not
|
||||
identify it, because a native Wayland app inherits both. `XInitThreads()` runs only when `DISPLAY` is
|
||||
set.
|
||||
- **`GDK_BACKEND=x11…` opt-in path**: sets `DRI_PRIME=1` (and the NVIDIA PRIME variables when
|
||||
`/proc/driver/nvidia/version` exists), calls `XInitThreads()`, and deliberately does **not** disable
|
||||
compositing (the comment: the WebKit2GTK XWayland bug appears fixed in WebKit2GTK ≥ 2.42, and leaving
|
||||
compositing on keeps hardware acceleration for Device / Setup Wizard / login pages).
|
||||
compositing, which keeps hardware acceleration for Device / Setup Wizard / login pages.
|
||||
|
||||
Runtime backend detection elsewhere uses Orca's `is_running_on_wayland()` / `is_running_on_x11()`
|
||||
(see `references/platforms.md`), not these environment variables.
|
||||
|
||||
**Pitfall.**
|
||||
- **Rule:** Set `WEBKIT_DISABLE_COMPOSITING_MODE` only on the default path in Wayland sessions,
|
||||
non-replacing (`setenv(..., false)`, so a value the user already exported wins), before GTK
|
||||
initialises; never unconditionally. Keep JS-side fallbacks so a page never
|
||||
- **Rule:** On NVIDIA reach for `WEBKIT_DMABUF_RENDERER_FORCE_SHM` before
|
||||
`WEBKIT_DISABLE_COMPOSITING_MODE`. **Why:** the proprietary driver cannot back WebKit's hardware
|
||||
DMABUF renderer, leaving web views blank or slow; FORCE_SHM keeps accelerated compositing and only
|
||||
skips the hardware path, while disabling compositing drops it. Threaded scrolling is a separate,
|
||||
release-dependent default (off in 2.44/2.45, on from 2.46). `WEBKIT_DISABLE_DMABUF_RENDERER` is not a
|
||||
substitute — it is read first, leaves the transport set empty (no compositing from 2.44 on, where the
|
||||
X11/Wayland fallback renderers were removed) and has a reported crash path. On Debian/Ubuntu and
|
||||
openSUSE the `isNVIDIA()` patch returns before the FORCE_SHM check, so `WEBKIT_FORCE_DMABUF_RENDERER=1`
|
||||
must clear it first; that variable exists only in those patches. WebKit reads all of these once per
|
||||
process, before the first WebView.
|
||||
The test is `/proc/driver/nvidia/version`, so a hybrid laptop that renders on its iGPU also takes the
|
||||
shared-memory path, which is a per-frame GPU readback rather than zero-copy; exporting
|
||||
`WEBKIT_DMABUF_RENDERER_FORCE_SHM=0` in the environment restores the hardware path.
|
||||
```cpp
|
||||
// Wrong: kills compositing and threaded scrolling (and can crash on modern WebKit)
|
||||
::setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1", false);
|
||||
// Right: keep compositing, skip only the hardware DMABUF path
|
||||
if (::access("/proc/driver/nvidia/version", F_OK) == 0 && WebView::SupportsForceSharedMemory()) {
|
||||
::setenv("WEBKIT_DMABUF_RENDERER_FORCE_SHM", "1", /*replace*/ false);
|
||||
::setenv("WEBKIT_FORCE_DMABUF_RENDERER", "1", /*replace*/ false);
|
||||
}
|
||||
```
|
||||
Cite: WebKit 2.52.6 `Source/WebKit/UIProcess/gtk/AcceleratedBackingStore.cpp`
|
||||
(`rendererBufferTransportMode()`), Debian `disable-nvidia-dmabuf.patch`, WebKit bug 262607.
|
||||
- **Rule:** Set `WEBKIT_DISABLE_COMPOSITING_MODE` only on the default path, in the branch where wx
|
||||
lacks EGL and Orca forces `GDK_BACKEND=x11`, non-replacing (`setenv(..., false)`, so a value the user
|
||||
already exported wins), before GTK
|
||||
initialises; never unconditionally, and only for the WebKitGTK releases that need it (older than
|
||||
2.42, `WebView::NeedsCompositingWorkaround()`). Keep JS-side fallbacks so a page never
|
||||
waits forever for a C++→JS call.
|
||||
**Why:** WebKit2GTK accelerated compositing could fail silently under XWayland, leaving web views
|
||||
blank (Setup Wizard stuck on "Loading..."); disabling it everywhere needlessly drops hardware
|
||||
acceleration; without the JS timeout the user is stuck when the signal is lost.
|
||||
**Why:** WebKit2GTK accelerated compositing could fail silently under XWayland before 2.42, leaving
|
||||
web views blank (Setup Wizard stuck on "Loading..."); the variable also turns off threaded scrolling,
|
||||
so applying it on a newer WebKit needlessly drops both GPU compositing and async scrolling (the
|
||||
Device tab loses its smoothness); without the JS timeout the user is stuck when the signal is lost.
|
||||
```cpp
|
||||
// Wrong: unconditional, kills HW acceleration everywhere
|
||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", false);
|
||||
// Right (default path only): non-replacing, before GTK init, Wayland session with XWayland
|
||||
if (display && *display && wayland && *wayland)
|
||||
// Right (default path only): non-replacing, before GTK init, WebKitGTK < 2.42,
|
||||
// and only in the branch that forces GDK_BACKEND=x11 (wx without EGL), the
|
||||
// case that runs through XWayland
|
||||
if (WebView::NeedsCompositingWorkaround() && wayland && *wayland)
|
||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /*replace*/ false);
|
||||
```
|
||||
Cite: c12912e0df (`src/OrcaSlicer.cpp`; `resources/web/guide/0/load.js` `OnInit`
|
||||
|
||||
@@ -288,6 +288,19 @@ if (APPLE)
|
||||
# Type"); Ninja/-Wall does not, and under -Werror it fails Xcode builds on code CI accepts.
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_64_TO_32_BIT_CONVERSION "NO")
|
||||
|
||||
# The Xcode generator ignores CMAKE_<LANG>_COMPILER_LAUNCHER, so hand the launcher to
|
||||
# Xcode's own Compiler Launcher setting, which wraps every clang compile (C, C++,
|
||||
# Objective-C++ and the precompiled header) with one program: the C++ launcher wins when
|
||||
# both are set. The setting takes a single program path, not a CMake launcher list.
|
||||
# Xcode versions without it ignore it and build uncached.
|
||||
if (CMAKE_GENERATOR STREQUAL "Xcode")
|
||||
if (CMAKE_CXX_COMPILER_LAUNCHER)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_C_COMPILER_LAUNCHER "${CMAKE_CXX_COMPILER_LAUNCHER}")
|
||||
elseif (CMAKE_C_COMPILER_LAUNCHER)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_C_COMPILER_LAUNCHER "${CMAKE_C_COMPILER_LAUNCHER}")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
message(STATUS "Orca: IS_CROSS_COMPILE: ${IS_CROSS_COMPILE}")
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN")
|
||||
|
||||
+15
-1
@@ -22,7 +22,7 @@ function usage() {
|
||||
echo " -g: run the requested build steps inside a Docker/Podman Ubuntu 24.04 container similar to the GitHub Actions Linux runner"
|
||||
echo " -h: prints this help text"
|
||||
echo " -i: build the Orca Slicer AppImage (optional)"
|
||||
echo " -p: boost ccache hit rate by disabling precompiled headers (default: ON)"
|
||||
echo " -p: disable precompiled headers (on by default): more ccache hits after header changes, slower misses"
|
||||
echo " -r: skip RAM and disk checks (low RAM compiling)"
|
||||
echo " -s: build the Orca Slicer (optional)"
|
||||
echo " -t: build tests (optional), requires -s flag"
|
||||
@@ -549,6 +549,20 @@ fi
|
||||
if [ -n "${CMAKE_CCACHE}" ] ; then
|
||||
echo "${CMAKE_CCACHE} found, enabling compiler caching..."
|
||||
export CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER="${CMAKE_CCACHE}" -DCMAKE_CXX_COMPILER_LAUNCHER="${CMAKE_CCACHE}")
|
||||
# The same ccache settings as the CI build. Without this sloppiness ccache refuses every
|
||||
# compile that uses the precompiled header, which is most of OrcaSlicer, and skips headers
|
||||
# a checkout has just written. It is added to the sloppiness already configured, and
|
||||
# CCACHE_SLOPPINESS or CCACHE_(NO)DEPEND set in the environment win.
|
||||
if [[ "$(basename "${CMAKE_CCACHE}")" == ccache ]] ; then
|
||||
if [[ -z "${CCACHE_SLOPPINESS+x}" ]] ; then
|
||||
CCACHE_SLOPPINESS=$("${CMAKE_CCACHE}" --get-config sloppiness 2>/dev/null || true)
|
||||
export CCACHE_SLOPPINESS="${CCACHE_SLOPPINESS:+${CCACHE_SLOPPINESS},}pch_defines,time_macros,include_file_mtime,include_file_ctime"
|
||||
fi
|
||||
# Hash the includes the compiler reports instead of preprocessing every miss.
|
||||
if [[ -z "${CCACHE_DEPEND+x}${CCACHE_NODEPEND+x}" ]] ; then
|
||||
export CCACHE_DEPEND=1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Note: ccache or sccache are not found. Install either of them for faster rebuilds."
|
||||
fi
|
||||
|
||||
@@ -111,6 +111,54 @@ else
|
||||
export CMAKE_POLICY_COMPAT=""
|
||||
fi
|
||||
|
||||
# Compiler cache, as in build_linux.sh. The program comes from CMAKE_CCACHE, else from a
|
||||
# launcher already set in the environment (the CI build sets ccache there), else sccache or
|
||||
# ccache from PATH; one program serves both languages. Each run applies its own choice to the
|
||||
# build directories, replacing a launcher an earlier run or a hand-run configure recorded.
|
||||
CMAKE_CCACHE_ARGS=()
|
||||
CCACHE_VAR=""
|
||||
for var in CMAKE_CCACHE CMAKE_CXX_COMPILER_LAUNCHER CMAKE_C_COMPILER_LAUNCHER; do
|
||||
if [ -n "${!var:-}" ]; then
|
||||
CCACHE_VAR=$var
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -n "$CCACHE_VAR" ]; then
|
||||
CCACHE_PROGRAM=${!CCACHE_VAR}
|
||||
echo "Checking ${CCACHE_VAR} environment variable for compiler cache program..."
|
||||
CMAKE_CCACHE=$(command -v "${CCACHE_PROGRAM}") || {
|
||||
echo "${CCACHE_VAR} environment variable is set to '${CCACHE_PROGRAM}' but it was not found in PATH."
|
||||
CMAKE_CCACHE=""
|
||||
}
|
||||
elif command -v sccache >/dev/null 2>&1 ; then
|
||||
CMAKE_CCACHE=$(command -v sccache)
|
||||
elif command -v ccache >/dev/null 2>&1 ; then
|
||||
CMAKE_CCACHE=$(command -v ccache)
|
||||
fi
|
||||
if [ -n "${CMAKE_CCACHE}" ] ; then
|
||||
echo "${CMAKE_CCACHE} found, enabling compiler caching..."
|
||||
CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER="${CMAKE_CCACHE}" -DCMAKE_CXX_COMPILER_LAUNCHER="${CMAKE_CCACHE}")
|
||||
# The same ccache settings as the CI build. Without this sloppiness ccache refuses every
|
||||
# compile that uses the precompiled header, which is most of OrcaSlicer, and skips headers
|
||||
# a checkout has just written. It is added to the sloppiness already configured, and
|
||||
# CCACHE_SLOPPINESS or CCACHE_(NO)DEPEND set in the environment win.
|
||||
if [[ "$(basename "${CMAKE_CCACHE}")" == ccache ]] ; then
|
||||
if [[ -z "${CCACHE_SLOPPINESS+x}" ]] ; then
|
||||
CCACHE_SLOPPINESS=$("${CMAKE_CCACHE}" --get-config sloppiness 2>/dev/null || true)
|
||||
export CCACHE_SLOPPINESS="${CCACHE_SLOPPINESS:+${CCACHE_SLOPPINESS},}pch_defines,time_macros,include_file_mtime,include_file_ctime"
|
||||
fi
|
||||
# Hash the includes the compiler reports instead of preprocessing every miss.
|
||||
if [[ -z "${CCACHE_DEPEND+x}${CCACHE_NODEPEND+x}" ]] ; then
|
||||
export CCACHE_DEPEND=1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Note: ccache or sccache are not found. Install either of them for faster rebuilds."
|
||||
# Clear a launcher recorded by an earlier configure. The entries must be set empty rather
|
||||
# than removed: CMake fills a missing launcher entry from the environment.
|
||||
CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER:STRING= -DCMAKE_CXX_COMPILER_LAUNCHER:STRING=)
|
||||
fi
|
||||
|
||||
echo "Build params:"
|
||||
echo " - ARCH: $ARCH"
|
||||
echo " - BUILD_CONFIG: $BUILD_CONFIG"
|
||||
@@ -162,6 +210,7 @@ function build_deps() {
|
||||
-DCMAKE_OSX_ARCHITECTURES:STRING="${_ARCH}" \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
|
||||
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
|
||||
"${CMAKE_CCACHE_ARGS[@]}" \
|
||||
${CMAKE_POLICY_COMPAT}
|
||||
fi
|
||||
cmake --build . --config "$BUILD_CONFIG" --target deps
|
||||
@@ -264,6 +313,7 @@ function build_slicer() {
|
||||
-DCMAKE_OSX_ARCHITECTURES="${_ARCH}" \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
|
||||
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
|
||||
"${CMAKE_CCACHE_ARGS[@]}" \
|
||||
${CMAKE_POLICY_COMPAT}
|
||||
fi
|
||||
cmake --build . --config "$BUILD_CONFIG" --target "$SLICER_BUILD_TARGET"
|
||||
|
||||
Vendored
+2
-17
@@ -1,22 +1,7 @@
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
if (JPEG_VERSION STREQUAL "6")
|
||||
message("Using Jpeg Lib 62")
|
||||
set(jpeg_flag "")
|
||||
elseif (JPEG_VERSION STREQUAL "7")
|
||||
message("Using Jpeg Lib 70")
|
||||
set(jpeg_flag "-DWITH_JPEG7=ON")
|
||||
else ()
|
||||
message("Using Jpeg Lib 80")
|
||||
set(jpeg_flag "-DWITH_JPEG8=ON")
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
orcaslicer_add_cmake_project(JPEG
|
||||
URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.0.1.zip
|
||||
URL_HASH SHA256=d6d99e693366bc03897677650e8b2dfa76b5d6c54e2c9e70c03f0af821b0a52f
|
||||
DEPENDS ${ZLIB_PKG}
|
||||
URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.2.0.zip
|
||||
URL_HASH SHA256=1b4c2cb6cba1ca5e53b9e85eeeab02154cab4b70894c96e97a9c747a26d19d63
|
||||
CMAKE_ARGS
|
||||
-DENABLE_SHARED=OFF
|
||||
-DENABLE_STATIC=ON
|
||||
${jpeg_flag}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
# ChromaStack - High Level Design
|
||||
|
||||
## Purpose and scope
|
||||
|
||||
ChromaStack prints a colour picture on a single face of a model: a few filaments are printed one on
|
||||
top of another, the printer swaps filament at fixed layer boundaries, and the *height* of every
|
||||
pixel decides how many of those layers reach it. Filament is translucent, so a pixel that stops
|
||||
part-way through a run shows a blend of that run's filament and whatever is under it, which is
|
||||
where the colours come from - far more of them than there are filaments loaded.
|
||||
|
||||
This is that model inside OrcaSlicer, as a mode of a texture displacement layer. The picture and
|
||||
the filament stack are the input; the output is a grayscale height map and a set of filament swaps.
|
||||
Nothing about it is a new pipeline: the height map is handed to the layer the user painted, so the
|
||||
existing projection, preview and bake turn it into real geometry, and the swaps are handed to the
|
||||
object's height ranges, which is the slicer's own way of printing a Z band in another filament.
|
||||
|
||||
It does not import other filament-painting tools' project files, and it does not choose the filaments or
|
||||
their order - that is the user's artistic decision, and the panel exists to make its result
|
||||
visible before the print.
|
||||
|
||||
## The colour model
|
||||
|
||||
`libslic3r/ChromaStack.hpp` holds the whole of it, free of any GUI dependency, which is what lets the
|
||||
solve run against plain image bytes and be unit tested.
|
||||
|
||||
A **stack** (`ChromaStack`) is a list of **bands** (`ChromaBand`), bottom-up: a filament and
|
||||
how many layers it gets. Alongside them it carries the print's layer height, which the colours
|
||||
depend on directly, and the **base**: how many layers of solid backing the picture sits on.
|
||||
|
||||
The base is not a band. It is a measurement of the model - everything below the painted face, often
|
||||
a hundred layers or more on a tall part - while a band is a handful of layers the user chooses. It
|
||||
prints in the bottom band's filament, because that is what is loaded when the print starts, so it
|
||||
extends that band's height range down to the first layer instead of being a swap of its own. Only
|
||||
the bands are quantized into the height map, which is why the 255-layer limit
|
||||
(`CHROMA_MAX_RELIEF_LAYERS`) bounds the picture and not the stack.
|
||||
|
||||
Each filament carries its **transmission distance** (TD), the usual measure for how deep
|
||||
light gets into it: about 1 for an opaque filament, 4 to 7 for a usefully translucent one, 8 and
|
||||
above for a nearly clear one. TD lives in the filament preset, as
|
||||
`filament_transmission_distance` - it is a property of the material, so it belongs with the
|
||||
material and ships with a profile rather than being retyped per project. It has no effect on
|
||||
slicing.
|
||||
|
||||
`chroma_palette()` turns a stack into the colour it shows at every possible column height, which
|
||||
is all the solver ever needs. The model is Beer-Lambert absorption, layer by layer: a layer of
|
||||
thickness `h` in a filament of transmission distance `TD` passes
|
||||
|
||||
T = exp(-h / (CHROMA_TD_OPAQUE_FRACTION * TD / 3))
|
||||
|
||||
of the light from under it and shows its own colour with the rest, so walking up from an opaque
|
||||
base,
|
||||
|
||||
C_k = (1 - T_k) * c_k + T_k * C_(k-1), C_0 = c_1
|
||||
|
||||
The constants say what TD means in physical terms: a filament stops showing what is behind it at
|
||||
about a tenth of its TD number (`CHROMA_TD_OPAQUE_FRACTION`), and three e-folding lengths of
|
||||
absorption pass 5% of the light, which is where the eye stops seeing through. Those are calibrated
|
||||
against how the community's TD step prints behave, not derived from first principles - TD is an
|
||||
empirical scale.
|
||||
|
||||
How fast that happens is the single most useful fact about a stack, and `chroma_layers_to_opaque()`
|
||||
is it: a filament hides what is under it at about a tenth of its TD, so how many *layers* that takes
|
||||
depends entirely on how thick they are. TD 4 covers in two layers at 0.2 mm and in five at 0.08 mm.
|
||||
Below about two layers a band has no intermediate colours at all - the picture can only use the
|
||||
filament or not use it - which is why an otherwise sound stack prints as flat steps at a coarse
|
||||
layer height. The panel reports the number under the palette ramp, and warns when it falls below
|
||||
two, because nothing else makes it visible: the filaments look fine, the bands look long enough, and
|
||||
the result looks like the colour model is wrong when it is the layer height.
|
||||
|
||||
How many of a picture's layers actually earn their place is a second question, and
|
||||
`chroma_distinct_levels()` answers it: a band eventually hides what is under it, and another layer of
|
||||
it then shows the colour the one below already showed. It is measured against the palette rather than
|
||||
against TD, because how long a band keeps paying depends on what is under it as much as on the
|
||||
filament - a light band over a dark one is finished in four or five layers, while a dark one over a
|
||||
light one goes on moving the colour perceptibly well past the depth at which it is nominally opaque,
|
||||
since the last few percent of light coming through still shifts a near-black colour. `Trim` shortens
|
||||
each band to its last layer that still changes anything.
|
||||
|
||||
Beer-Lambert is also the only model that stays self-consistent applied per layer: transmittance
|
||||
multiplies, so a run of n layers of one filament passes `exp(-n h / d)` whether it is composited in
|
||||
one step or n, and the palette therefore does not depend on how the stack happens to be cut into
|
||||
bands. A unit test asserts exactly that. Compositing is done on linear light, because the quantity
|
||||
being attenuated is light rather than an sRGB code.
|
||||
|
||||
The base is assumed opaque. That is what makes a column's colour depend only on the layers above it,
|
||||
and it is why the panel warns when the base is thin.
|
||||
|
||||
## The solve
|
||||
|
||||
`chroma_solve()` maps each pixel of the picture to the column height whose palette colour is
|
||||
closest to it, perceptually: both sides go to CIELAB and the match is a CIE76 distance. A 1-D
|
||||
palette of a few dozen entries makes the extra accuracy of CIEDE2000 hard to see and the cost of it
|
||||
easy to feel - several hundred million comparisons for a large picture - so the cheaper metric is
|
||||
the deliberate choice here, unlike the texture colour quantizer, which matches against a
|
||||
two-dimensional palette of filament mixes and uses a precomputed CIEDE2000 cube.
|
||||
|
||||
Optional Floyd-Steinberg error diffusion, serpentine, trades the bands a photograph shows across a
|
||||
sky or a cheek for fine stipple. Off by default, since stipple is the artifact rather than the cure
|
||||
for flat-coloured artwork.
|
||||
|
||||
Three things come back: the per-pixel layer count, the same thing as an 8-bit grayscale height map
|
||||
(0 at the base, 255 at the top of the stack), and the palette colours chosen, as an RGB image. The
|
||||
quantization belongs to the solve rather than to the displacement because the relief's steps have
|
||||
to land on layer boundaries, and a grey level is the only channel the displacement reads.
|
||||
|
||||
The solve also reports the mean and worst colour difference it had to accept. That is the only
|
||||
honest answer to "will this stack reproduce my picture": under about 5 is a good match, over about
|
||||
15 means the filaments cannot reach the picture's colours and no number of layers will help.
|
||||
|
||||
## How it attaches to a texture displacement layer
|
||||
|
||||
A ChromaStack layer is an ordinary `TextureDisplacementLayer` whose height map is computed rather than
|
||||
imported:
|
||||
|
||||
- `chroma_source` keeps the picture the user chose, so the solve can be redone whenever the stack,
|
||||
the layer height or the picture changes;
|
||||
- `chroma` is the stack;
|
||||
- `image_data` - the same field every other layer uses - holds whatever the last solve produced.
|
||||
|
||||
So everything downstream needs no changes at all and knows nothing about colour stacks. It also
|
||||
means the solve writes `depth_mm` (the relief is exactly as tall as the layers above the base) and
|
||||
pins `invert`, `midlevel` and `smoothing`: blurring a solved height map moves its steps off the
|
||||
layer boundaries they were solved for.
|
||||
|
||||
Colour by filament swap and the layer's own `color_enabled` (one filament per triangle, via
|
||||
`mmu_segmentation_facets`) are mutually exclusive, and the panel enforces it. They are two
|
||||
different ways of colouring the same surface, and painting the relief per triangle would hide the
|
||||
very stack the heights were solved against.
|
||||
|
||||
## The filament swaps
|
||||
|
||||
`chroma_ranges()` turns the bands into Z bands in the object's own coordinates, merging any two
|
||||
adjacent bands that share a filament, since nothing is swapped between them. The panel writes them
|
||||
onto the object as `layer_config_ranges`, each range carrying `extruder` - which
|
||||
`apply_to_print_region_config()` spreads over every feature filament the range does not otherwise
|
||||
override, i.e. the whole band prints in that filament - and `layer_height`, both because the solve
|
||||
is only valid at the height it was run at and because `layer_height_profile_from_ranges()` reads
|
||||
that option without checking it is there.
|
||||
|
||||
Height ranges rather than colour-change custom G-code: they are object-relative, so they survive
|
||||
the object being moved, they are saved in the project file, and they appear in the object list where
|
||||
the user can see and edit them. The cost is that an object can only have one set of them, so
|
||||
applying a stack replaces whatever ranges the object had, with a confirmation when it had any.
|
||||
|
||||
`ObjectList::rebuild_layer_ranges()` mirrors them in the object tree without touching the
|
||||
selection - changing the selection while a gizmo is open tears the gizmo down under the user.
|
||||
|
||||
## Geometry the model assumes
|
||||
|
||||
A filament swap happens at one Z across the whole plate, and the relief grows along the painted
|
||||
surface's normal. The two agree only where that surface is horizontal, so:
|
||||
|
||||
- the painted patch's height above the bottom of the object is what the base layer count has to
|
||||
match, and the panel measures it from the patch and offers the number rather than leaving the user
|
||||
to work it out;
|
||||
- a patch that is not flat in Z gets a warning, because its colours will only be right where it is
|
||||
level. The relief itself is still computed, since a gently domed plaque is a legitimate thing to
|
||||
print and only the user can judge how much error is acceptable.
|
||||
|
||||
The model itself is the user's: the stack needs a slab of the base thickness with a face to paint,
|
||||
and nothing here creates or thickens geometry.
|
||||
|
||||
## Seeing the colours
|
||||
|
||||
The colours are blends of translucent filaments. No single filament makes them, so they cannot be
|
||||
expressed as the per-triangle filament assignment the 3D scene paints with, and there is nothing for
|
||||
the ordinary view to draw. They exist in two places: the sliced preview, which shows the filament
|
||||
bands as they print, and an overlay the gizmo draws over whatever surface is on screen.
|
||||
|
||||
That overlay is a shader, `resources/shaders/*/chromastack.*`, and it reads the colour from each
|
||||
fragment's own **print Z** rather than from the height map or from anything in the mesh. A filament
|
||||
swap happens at one Z across the whole plate, so the layer a point sits in is what decides its
|
||||
colour whatever the picture says. Two things follow. A band edge lands exactly on the layer boundary
|
||||
however coarse the triangles are - a per-triangle colouring puts it wherever the mesh happens to
|
||||
have an edge. And it keeps working after a bake, when the relief is real geometry, the paint has
|
||||
been cleared and the height map is attached to nothing on screen; the overlay is built from the live
|
||||
preview's displaced mesh before a bake and from the volume's own mesh after one.
|
||||
|
||||
The stack's colours reach the shader as a texture of one texel per printed layer, because a base can
|
||||
be several hundred layers and no uniform array holds that. The mesh carries nothing about the stack,
|
||||
so editing the stack costs a palette upload rather than a mesh rebuild.
|
||||
|
||||
On top of the colour the shader tries to look like printed plastic, because a flat shaded colour
|
||||
reads as a rendering and the whole point of the overlay is judging a print:
|
||||
|
||||
- the terminator is **wrapped** and the grazing angles **glow**, both scaled by the transmission
|
||||
distance of the filament on top - filament translucent enough to be worth stacking scatters light
|
||||
rather than reflecting it, and an opaque stack correctly gets neither;
|
||||
- the walls carry **layer lines**, a groove at every layer boundary that also modulates the
|
||||
specular, so a wall glints in stripes as a printed one does. Terraces get none: a terrace is one
|
||||
layer's top surface and has no lines to show.
|
||||
|
||||
Both effects are driven by the fragment's height, so they are free of any texture or UV, and both
|
||||
fade out through `fwidth` once a pixel covers more than about half a layer - lines finer than a
|
||||
pixel are noise, and a colour read from one layer out of the dozen under a pixel flickers as the
|
||||
camera moves. The palette lookup slides off the texel centres over the same range so the linear
|
||||
filter averages the bands a pixel covers instead of picking one of them.
|
||||
|
||||
The overlay is drawn pulled forward and without depth writes, the same pass the painted-colour
|
||||
overlay uses, since it is the same surface as whatever is underneath it.
|
||||
|
||||
## What the panel shows
|
||||
|
||||
The kind of a layer is the layer's own, chosen when it is added and fixed from then on: **Add layer**
|
||||
offers *Relief layer* or *ChromaStack picture*, and the card carries its kind as a chip. There is no
|
||||
panel-wide mode, so one object can hold a picture on its face and a knurl on its rim, and a card
|
||||
never shows a control that cannot do anything on it.
|
||||
|
||||
What the two cards do not share:
|
||||
|
||||
| Control | Relief card | ChromaStack card |
|
||||
| --- | --- | --- |
|
||||
| Depth | slider | the solve writes it; the card prints the result |
|
||||
| Tile size | repeats the pattern | **Picture size**, the same number named for what it does |
|
||||
| Midlevel, Smoothing, Invert, Edge fade | present | absent: each would move the steps off the layer boundaries |
|
||||
| Colors, Mix filaments, Denoise | one filament per triangle | absent: swaps are how this card colours |
|
||||
| Tile, tile method | present | absent: one copy, never tiled |
|
||||
| Blend | four modes | absent: it is the top layer and replaces what is under it |
|
||||
| Picture, prediction, stack, palette, match, Dither, Solve, Apply swaps | absent | present |
|
||||
|
||||
A ChromaStack card has no grip and cannot be reordered: its colours come from swapping filament at a
|
||||
height, so anything printed above it would bury them. An object may hold only one, refused when the
|
||||
layer is added rather than when the swaps are applied, because the swaps are the object's own height
|
||||
ranges and there is one set of those.
|
||||
|
||||
The card itself, top to bottom: what it is in one line, the picture and the predicted print side by
|
||||
side at the same size, the match figures, the base with the measurement from the painted face offered
|
||||
beside it, a row per band - swatch, filament, layer count, TD - the colours the stack reaches as a
|
||||
ramp, the totals, dither with the layer height it is solved against, and the two actions. Base and
|
||||
band counts are typed fields rather than sliders: a base is measured in hundreds of layers and a band
|
||||
in single digits, and dragging past the end of a slider's range is how a stack silently loses its
|
||||
backing.
|
||||
|
||||
The ramp is computed from the stack alone, before any solve. It is what answers "can these filaments
|
||||
print this picture" without spending a solve to find out they cannot: a short or flat ramp means the
|
||||
filaments are too alike, or too opaque, to give the picture anything to work with.
|
||||
|
||||
A per-slot flag records that the stack has been edited since the last solve, because a stale height
|
||||
map looks exactly like a good one.
|
||||
+1336
-56
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+4922
-1909
File diff suppressed because it is too large
Load Diff
+6162
-2709
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><rect x="7" y="12" width="1" height="1" style="fill:#fefefe;"/><path d="M4.5,5.5c0-1.71,1.43-3.09,3.16-3,1.62.08,2.84,1.54,2.84,3.17h0c0,.61-.25,1.2-.69,1.62l-2.31,2.24v.97" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/></svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><rect x="1.5" y="3.5" width="12" height="8" rx="1" ry="1" style="fill:none; stroke:#fefefe; stroke-linecap:round; stroke-linejoin:round;"/><path d="M3,5h1v1h-1Z M5,5h1v1h-1Z M7,5h1v1h-1Z M9,5h1v1h-1Z M11,5h1v1h-1Z M4,7h1v1h-1Z M6,7h1v1h-1Z M8,7h1v1h-1Z M10,7h1v1h-1Z M5,9h5v1h-5Z" style="fill:#fefefe;"/></svg>
|
||||
|
Before Width: | Height: | Size: 366 B After Width: | Height: | Size: 415 B |
@@ -592,6 +592,10 @@
|
||||
"name": "SUNLU PETG @base",
|
||||
"sub_path": "filament/SUNLU/SUNLU PETG @base.json"
|
||||
},
|
||||
{
|
||||
"name": "School House Spools PETG @base",
|
||||
"sub_path": "filament/SchoolHouseSpools/School House Spools PETG @base.json"
|
||||
},
|
||||
{
|
||||
"name": "eSUN PETG @base",
|
||||
"sub_path": "filament/eSUN/eSUN PETG @base.json"
|
||||
@@ -1096,6 +1100,10 @@
|
||||
"name": "SUNLU Wood PLA @base",
|
||||
"sub_path": "filament/SUNLU/SUNLU Wood PLA @base.json"
|
||||
},
|
||||
{
|
||||
"name": "School House Spools PLA @base",
|
||||
"sub_path": "filament/SchoolHouseSpools/School House Spools PLA @base.json"
|
||||
},
|
||||
{
|
||||
"name": "Valment PLA @base",
|
||||
"sub_path": "filament/Valment/Valment PLA @base.json"
|
||||
@@ -2260,6 +2268,10 @@
|
||||
"name": "SUNLU PETG @System",
|
||||
"sub_path": "filament/SUNLU/SUNLU PETG @System.json"
|
||||
},
|
||||
{
|
||||
"name": "School House Spools PETG @System",
|
||||
"sub_path": "filament/SchoolHouseSpools/School House Spools PETG @System.json"
|
||||
},
|
||||
{
|
||||
"name": "eSUN PETG @System",
|
||||
"sub_path": "filament/eSUN/eSUN PETG @System.json"
|
||||
@@ -3812,6 +3824,10 @@
|
||||
"name": "SUNLU Wood PLA @System",
|
||||
"sub_path": "filament/SUNLU/SUNLU Wood PLA @System.json"
|
||||
},
|
||||
{
|
||||
"name": "School House Spools PLA @System",
|
||||
"sub_path": "filament/SchoolHouseSpools/School House Spools PLA @System.json"
|
||||
},
|
||||
{
|
||||
"name": "Valment PLA @System",
|
||||
"sub_path": "filament/Valment/Valment PLA @System.json"
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "School House Spools PETG @System",
|
||||
"inherits": "School House Spools PETG @base",
|
||||
"from": "system",
|
||||
"setting_id": "RSu2orfhR8ktT36h",
|
||||
"instantiation": "true",
|
||||
"filament_max_volumetric_speed": [
|
||||
"13"
|
||||
],
|
||||
"filament_long_retractions_when_cut": [
|
||||
"1"
|
||||
],
|
||||
"filament_retraction_distances_when_cut": [
|
||||
"18"
|
||||
],
|
||||
"compatible_printers": []
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "School House Spools PETG @base",
|
||||
"inherits": "fdm_filament_pet",
|
||||
"from": "system",
|
||||
"filament_id": "OFnaCGQ9",
|
||||
"instantiation": "false",
|
||||
"cool_plate_temp": [
|
||||
"0"
|
||||
],
|
||||
"cool_plate_temp_initial_layer": [
|
||||
"0"
|
||||
],
|
||||
"eng_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"eng_plate_temp_initial_layer": [
|
||||
"70"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"30"
|
||||
],
|
||||
"fan_max_speed": [
|
||||
"40"
|
||||
],
|
||||
"fan_min_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_cost": [
|
||||
"16.00"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.25"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.95"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"8"
|
||||
],
|
||||
"filament_multitool_ramming_flow": [
|
||||
"8"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"School House Spools"
|
||||
],
|
||||
"hot_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"hot_plate_temp_initial_layer": [
|
||||
"70"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"252"
|
||||
],
|
||||
"nozzle_temperature_initial_layer": [
|
||||
"252"
|
||||
],
|
||||
"nozzle_temperature_range_high": [
|
||||
"270"
|
||||
],
|
||||
"nozzle_temperature_range_low": [
|
||||
"230"
|
||||
],
|
||||
"overhang_fan_speed": [
|
||||
"90"
|
||||
],
|
||||
"overhang_fan_threshold": [
|
||||
"10%"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"12"
|
||||
],
|
||||
"textured_plate_temp": [
|
||||
"70"
|
||||
],
|
||||
"textured_plate_temp_initial_layer": [
|
||||
"70"
|
||||
]
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "School House Spools PLA @System",
|
||||
"inherits": "School House Spools PLA @base",
|
||||
"from": "system",
|
||||
"setting_id": "3rHJk6SVAvFzyHI0",
|
||||
"instantiation": "true",
|
||||
"filament_max_volumetric_speed": [
|
||||
"21"
|
||||
],
|
||||
"filament_long_retractions_when_cut": [
|
||||
"1"
|
||||
],
|
||||
"filament_retraction_distances_when_cut": [
|
||||
"18"
|
||||
],
|
||||
"compatible_printers": []
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "School House Spools PLA @base",
|
||||
"inherits": "fdm_filament_pla",
|
||||
"from": "system",
|
||||
"filament_id": "OFHzwmT9",
|
||||
"instantiation": "false",
|
||||
"filament_cost": [
|
||||
"17.00"
|
||||
],
|
||||
"filament_density": [
|
||||
"1.26"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"0.98"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"12"
|
||||
],
|
||||
"filament_vendor": [
|
||||
"School House Spools"
|
||||
],
|
||||
"filament_scarf_seam_type": [
|
||||
"none"
|
||||
],
|
||||
"filament_scarf_height": [
|
||||
"10%"
|
||||
],
|
||||
"filament_scarf_gap": [
|
||||
"0%"
|
||||
],
|
||||
"filament_scarf_length": [
|
||||
"10"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "RatRig",
|
||||
"version": "02.04.00.06",
|
||||
"version": "02.04.00.07",
|
||||
"force_update": "0",
|
||||
"description": "RatRig configurations",
|
||||
"machine_model_list": [
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"z_hop": [
|
||||
"0.2"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single] TOTAL_LAYER_COUNT={total_layer_count} X0={first_layer_print_min[0]} Y0={first_layer_print_min[1]} X1={first_layer_print_max[0]} Y1={first_layer_print_max[1]}",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n_ON_LAYER_CHANGE LAYER={layer_num + 1}",
|
||||
"change_filament_gcode": "M600",
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
"bed_exclude_area": [
|
||||
"0x0"
|
||||
],
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] BED_TEMP=[bed_temperature_initial_layer_single]",
|
||||
"machine_start_gcode": "START_PRINT EXTRUDER_TEMP=[nozzle_temperature_initial_layer] EXTRUDER_OTHER_LAYER_TEMP=[nozzle_temperature] BED_TEMP=[bed_temperature_initial_layer_single]",
|
||||
"machine_end_gcode": "END_PRINT",
|
||||
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]",
|
||||
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\n",
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
#version 110
|
||||
|
||||
// What a ChromaStack print looks like, shaded per fragment.
|
||||
//
|
||||
// The colour is not a vertex attribute and not a texture on the surface: it is read from the
|
||||
// fragment's own height. A filament swap happens at one print Z across the whole plate, so the layer
|
||||
// a point sits in is what decides its colour - which means a band edge lands exactly on the layer
|
||||
// boundary however coarse the mesh is, and the same shader is right before a bake (on the preview's
|
||||
// displaced mesh) and after one (on the volume's own).
|
||||
//
|
||||
// On top of that it tries to look like printed plastic rather than like flat shaded colour: the
|
||||
// terminator is wrapped and the grazing angles glow, because filament translucent enough to be worth
|
||||
// stacking scatters light rather than reflecting it; and the walls carry layer lines, because that is
|
||||
// the first thing the eye uses to read a print as a print.
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
// The stack's colours, one texel per printed layer, bottom to top. A texture rather than a uniform
|
||||
// array because a base can be several hundred layers and no uniform array survives that.
|
||||
uniform sampler2D palette;
|
||||
uniform float palette_size; // texels in `palette`, i.e. the stack's total printed layers
|
||||
uniform float base_z; // world Z of the bottom of the object
|
||||
uniform float layer_height; // mm
|
||||
// How translucent the stack reads, 0 to 1, from the transmission distance of the filament on top.
|
||||
// It drives the wrap and the rim: an opaque stack should not glow.
|
||||
uniform float translucency;
|
||||
uniform float alpha;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec3 eye_normal;
|
||||
varying vec3 eye_position;
|
||||
varying vec3 world_normal;
|
||||
varying float world_z;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, vec3(0.0))))
|
||||
discard;
|
||||
|
||||
float z_rel = world_z - base_z;
|
||||
float layers = max(z_rel / layer_height, 0.0);
|
||||
// How much of a layer one pixel covers. Everything below is faded out as this grows: layer lines
|
||||
// finer than a pixel are noise, and a colour read from one layer out of the dozen under a pixel
|
||||
// flickers as the camera moves.
|
||||
float px = fwidth(layers);
|
||||
|
||||
// The layer this fragment sits in, 1-based. Up close that is an exact texel of the palette; once a
|
||||
// pixel spans a layer or more it slides to a continuous coordinate so the linear filter averages
|
||||
// the bands it covers instead of picking one of them at random.
|
||||
float stepped = clamp(ceil(layers - 1.0e-4), 1.0, palette_size) - 0.5;
|
||||
float smooth_ = clamp(layers, 0.5, palette_size - 0.5);
|
||||
float u = mix(stepped, smooth_, clamp(px - 0.5, 0.0, 1.0)) / palette_size;
|
||||
vec3 albedo = texture2D(palette, vec2(u, 0.5)).rgb;
|
||||
|
||||
vec3 N = normalize(eye_normal);
|
||||
vec3 V = normalize(-eye_position);
|
||||
|
||||
// Layer lines, on the walls only: a terrace is one layer's top surface and has none to show. The
|
||||
// groove runs round the part at every layer boundary, so it is a function of height alone.
|
||||
float wall = 1.0 - abs(world_normal.z);
|
||||
float phase = fract(layers);
|
||||
float groove = smoothstep(0.0, 0.16, phase) * smoothstep(1.0, 0.84, phase);
|
||||
groove = mix(groove, 0.65, clamp(px * 1.5, 0.0, 1.0)); // fade to flat when too fine to resolve
|
||||
float lines = mix(1.0, 0.84 + 0.16 * groove, wall * 0.9);
|
||||
albedo *= lines;
|
||||
|
||||
// Diffuse, with a wrapped terminator: light entering translucent filament comes back out a little
|
||||
// way round the curve, so the shaded side never goes as dark as an opaque surface's would.
|
||||
float wrap = 0.25 + 0.45 * translucency;
|
||||
float ndotl = dot(N, LIGHT_TOP_DIR);
|
||||
float diffuse = max((ndotl + wrap) / (1.0 + wrap), 0.0) * LIGHT_TOP_DIFFUSE;
|
||||
diffuse += max((dot(N, LIGHT_FRONT_DIR) + wrap) / (1.0 + wrap), 0.0) * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
// Specular, strongest along a layer line, which is what makes a printed wall glint in stripes
|
||||
// rather than evenly.
|
||||
float spec = LIGHT_TOP_SPECULAR * pow(max(dot(V, reflect(-LIGHT_TOP_DIR, N)), 0.0), LIGHT_TOP_SHININESS);
|
||||
spec *= mix(1.0, 0.35 + 0.65 * groove, wall * 0.9);
|
||||
|
||||
// Scattering out of the edges: a translucent part lights up where it is seen at a grazing angle
|
||||
// and where it is thin. Tinted by the filament, because what comes back out has been through it.
|
||||
float rim = pow(1.0 - clamp(dot(N, V), 0.0, 1.0), 4.0);
|
||||
|
||||
vec3 color = albedo * (INTENSITY_AMBIENT + diffuse) + vec3(spec) + albedo * rim * translucency * 0.55;
|
||||
gl_FragColor = vec4(color, alpha);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#version 110
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
// The volume's normal matrix in world space, so the fragment shader can tell a wall from a terrace -
|
||||
// which is what decides whether layer lines show at all.
|
||||
uniform mat3 world_normal_matrix;
|
||||
// Clipping plane, x = min z, y = max z (the FFF/SLA preview's top/bottom cut).
|
||||
uniform vec2 z_range;
|
||||
// Clipping plane, general orientation (the section view).
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
// GLModel's P3N3 layout, which is what init_from(indexed_triangle_set) produces - this shader needs
|
||||
// nothing per vertex that the mesh does not already carry, because the colour comes from the
|
||||
// fragment's own height rather than from anything baked into the vertices.
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec3 eye_normal;
|
||||
varying vec3 eye_position;
|
||||
varying vec3 world_normal;
|
||||
varying float world_z;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 model_pos = vec4(v_position, 1.0);
|
||||
vec4 world_pos = volume_world_matrix * model_pos;
|
||||
world_z = world_pos.z;
|
||||
world_normal = normalize(world_normal_matrix * v_normal);
|
||||
eye_normal = normalize(view_normal_matrix * v_normal);
|
||||
|
||||
vec4 eye_pos = view_model_matrix * model_pos;
|
||||
eye_position = eye_pos.xyz;
|
||||
gl_Position = projection_matrix * eye_pos;
|
||||
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
#version 140
|
||||
|
||||
// What a ChromaStack print looks like, shaded per fragment.
|
||||
//
|
||||
// The colour is not a vertex attribute and not a texture on the surface: it is read from the
|
||||
// fragment's own height. A filament swap happens at one print Z across the whole plate, so the layer
|
||||
// a point sits in is what decides its colour - which means a band edge lands exactly on the layer
|
||||
// boundary however coarse the mesh is, and the same shader is right before a bake (on the preview's
|
||||
// displaced mesh) and after one (on the volume's own).
|
||||
//
|
||||
// On top of that it tries to look like printed plastic rather than like flat shaded colour: the
|
||||
// terminator is wrapped and the grazing angles glow, because filament translucent enough to be worth
|
||||
// stacking scatters light rather than reflecting it; and the walls carry layer lines, because that is
|
||||
// the first thing the eye uses to read a print as a print.
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
// The stack's colours, one texel per printed layer, bottom to top. A texture rather than a uniform
|
||||
// array because a base can be several hundred layers and no uniform array survives that.
|
||||
uniform sampler2D palette;
|
||||
uniform float palette_size; // texels in `palette`, i.e. the stack's total printed layers
|
||||
uniform float base_z; // world Z of the bottom of the object
|
||||
uniform float layer_height; // mm
|
||||
// How translucent the stack reads, 0 to 1, from the transmission distance of the filament on top.
|
||||
// It drives the wrap and the rim: an opaque stack should not glow.
|
||||
uniform float translucency;
|
||||
uniform float alpha;
|
||||
|
||||
in vec3 clipping_planes_dots;
|
||||
in vec3 eye_normal;
|
||||
in vec3 eye_position;
|
||||
in vec3 world_normal;
|
||||
in float world_z;
|
||||
|
||||
out vec4 out_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, vec3(0.0))))
|
||||
discard;
|
||||
|
||||
float z_rel = world_z - base_z;
|
||||
float layers = max(z_rel / layer_height, 0.0);
|
||||
// How much of a layer one pixel covers. Everything below is faded out as this grows: layer lines
|
||||
// finer than a pixel are noise, and a colour read from one layer out of the dozen under a pixel
|
||||
// flickers as the camera moves.
|
||||
float px = fwidth(layers);
|
||||
|
||||
// The layer this fragment sits in, 1-based. Up close that is an exact texel of the palette; once a
|
||||
// pixel spans a layer or more it slides to a continuous coordinate so the linear filter averages
|
||||
// the bands it covers instead of picking one of them at random.
|
||||
float stepped = clamp(ceil(layers - 1.0e-4), 1.0, palette_size) - 0.5;
|
||||
float smooth_ = clamp(layers, 0.5, palette_size - 0.5);
|
||||
float u = mix(stepped, smooth_, clamp(px - 0.5, 0.0, 1.0)) / palette_size;
|
||||
vec3 albedo = texture(palette, vec2(u, 0.5)).rgb;
|
||||
|
||||
vec3 N = normalize(eye_normal);
|
||||
vec3 V = normalize(-eye_position);
|
||||
|
||||
// Layer lines, on the walls only: a terrace is one layer's top surface and has none to show. The
|
||||
// groove runs round the part at every layer boundary, so it is a function of height alone.
|
||||
float wall = 1.0 - abs(world_normal.z);
|
||||
float phase = fract(layers);
|
||||
float groove = smoothstep(0.0, 0.16, phase) * smoothstep(1.0, 0.84, phase);
|
||||
groove = mix(groove, 0.65, clamp(px * 1.5, 0.0, 1.0)); // fade to flat when too fine to resolve
|
||||
float lines = mix(1.0, 0.84 + 0.16 * groove, wall * 0.9);
|
||||
albedo *= lines;
|
||||
|
||||
// Diffuse, with a wrapped terminator: light entering translucent filament comes back out a little
|
||||
// way round the curve, so the shaded side never goes as dark as an opaque surface's would.
|
||||
float wrap = 0.25 + 0.45 * translucency;
|
||||
float ndotl = dot(N, LIGHT_TOP_DIR);
|
||||
float diffuse = max((ndotl + wrap) / (1.0 + wrap), 0.0) * LIGHT_TOP_DIFFUSE;
|
||||
diffuse += max((dot(N, LIGHT_FRONT_DIR) + wrap) / (1.0 + wrap), 0.0) * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
// Specular, strongest along a layer line, which is what makes a printed wall glint in stripes
|
||||
// rather than evenly.
|
||||
float spec = LIGHT_TOP_SPECULAR * pow(max(dot(V, reflect(-LIGHT_TOP_DIR, N)), 0.0), LIGHT_TOP_SHININESS);
|
||||
spec *= mix(1.0, 0.35 + 0.65 * groove, wall * 0.9);
|
||||
|
||||
// Scattering out of the edges: a translucent part lights up where it is seen at a grazing angle
|
||||
// and where it is thin. Tinted by the filament, because what comes back out has been through it.
|
||||
float rim = pow(1.0 - clamp(dot(N, V), 0.0, 1.0), 4.0);
|
||||
|
||||
vec3 color = albedo * (INTENSITY_AMBIENT + diffuse) + vec3(spec) + albedo * rim * translucency * 0.55;
|
||||
out_color = vec4(color, alpha);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#version 140
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
// The volume's normal matrix in world space, so the fragment shader can tell a wall from a terrace -
|
||||
// which is what decides whether layer lines show at all.
|
||||
uniform mat3 world_normal_matrix;
|
||||
// Clipping plane, x = min z, y = max z (the FFF/SLA preview's top/bottom cut).
|
||||
uniform vec2 z_range;
|
||||
// Clipping plane, general orientation (the section view).
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
// GLModel's P3N3 layout, which is what init_from(indexed_triangle_set) produces - this shader needs
|
||||
// nothing per vertex that the mesh does not already carry, because the colour comes from the
|
||||
// fragment's own height rather than from anything baked into the vertices.
|
||||
in vec3 v_position;
|
||||
in vec3 v_normal;
|
||||
|
||||
out vec3 clipping_planes_dots;
|
||||
out vec3 eye_normal;
|
||||
out vec3 eye_position;
|
||||
out vec3 world_normal;
|
||||
out float world_z;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 model_pos = vec4(v_position, 1.0);
|
||||
vec4 world_pos = volume_world_matrix * model_pos;
|
||||
world_z = world_pos.z;
|
||||
world_normal = normalize(world_normal_matrix * v_normal);
|
||||
eye_normal = normalize(view_normal_matrix * v_normal);
|
||||
|
||||
vec4 eye_pos = view_model_matrix * model_pos;
|
||||
eye_position = eye_pos.xyz;
|
||||
gl_Position = projection_matrix * eye_pos;
|
||||
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
<script src="../js/plugin-config-ui.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
<script src="./plugin-sort.js"></script>
|
||||
<script src="../js/fuzzy-search.js"></script>
|
||||
<script src="../../js/fuzzy-search.js"></script>
|
||||
<script src="./plugin-search.js"></script>
|
||||
</head>
|
||||
<body onLoad="OnInit()">
|
||||
|
||||
@@ -156,12 +156,16 @@ function SetCloudMode( bOn )
|
||||
|
||||
m_CloudMode=bOn;
|
||||
$("body").toggleClass("cloud-mode", bOn);
|
||||
|
||||
if( bOn )
|
||||
{
|
||||
$(".BtnItem").removeClass("BtnItemSelected");
|
||||
$(".BtnShortcut").addClass("BtnItemSelected");
|
||||
}
|
||||
}
|
||||
|
||||
function GotoCloud()
|
||||
{
|
||||
$(".BtnItem").removeClass("BtnItemSelected");
|
||||
$(".BtnShortcut").addClass("BtnItemSelected");
|
||||
SetCloudMode(true);
|
||||
SendSimpleCommand("homepage_open_cloud");
|
||||
}
|
||||
|
||||
@@ -336,10 +336,10 @@ modules:
|
||||
sha256: 3d22d46c566b1761a0e15ea397589b3a5f36ac09b7c785382e6470156c04247f
|
||||
dest: external-packages/PNG
|
||||
|
||||
# libjpeg-turbo 3.0.1
|
||||
# libjpeg-turbo 3.2.0
|
||||
- type: file
|
||||
url: https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.0.1.zip
|
||||
sha256: d6d99e693366bc03897677650e8b2dfa76b5d6c54e2c9e70c03f0af821b0a52f
|
||||
url: https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.2.0.zip
|
||||
sha256: 1b4c2cb6cba1ca5e53b9e85eeeab02154cab4b70894c96e97a9c747a26d19d63
|
||||
dest: external-packages/JPEG
|
||||
|
||||
# Freetype 2.12.1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Work-around https://gitlab.gnome.org/GNOME/gnome-build-meta/-/issues/754
|
||||
grep -q org.freedesktop.Platform.GL.nvidia /.flatpak-info && export WEBKIT_DISABLE_DMABUF_RENDERER=1
|
||||
# WebKit's NVIDIA DMABUF workaround (gnome-build-meta#754) is applied by the
|
||||
# binary itself, gated on the runtime WebKitGTK version (src/OrcaSlicer.cpp).
|
||||
|
||||
# Work-around https://github.com/bambulab/BambuStudio/issues/3440
|
||||
# Use LC_NUMERIC instead of LC_ALL to prevent decimal separator issues
|
||||
|
||||
+21
-15
@@ -163,6 +163,7 @@ using json = nlohmann::json;
|
||||
#include <X11/Xlib.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include "slic3r/GUI/Widgets/WebView.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef SLIC3R_GUI
|
||||
@@ -1156,8 +1157,10 @@ int CLI::run(int argc, char **argv)
|
||||
// opt-in path. The ~2020-era WebKit2GTK XWayland compositor bug it
|
||||
// worked around appears fixed in WebKit2GTK >= 2.42; leaving it
|
||||
// unset preserves WebKit hardware acceleration on Device / Setup
|
||||
// Wizard / login / store. The default path still applies it on
|
||||
// XWayland sessions as a conservative fallback for older WebKit.
|
||||
// Wizard / login / store. The default path applies it only where it
|
||||
// forces GDK_BACKEND=x11 below (a build without EGL, so the app really
|
||||
// runs through XWayland), and only when the linked WebKitGTK predates
|
||||
// the fix (< 2.42), as a conservative fallback for older WebKit.
|
||||
// ------------------------------------------------------------------
|
||||
{
|
||||
const char* gdk_backend = ::getenv("GDK_BACKEND");
|
||||
@@ -1165,6 +1168,14 @@ int CLI::run(int argc, char **argv)
|
||||
// honours the first backend in the comma-separated list).
|
||||
const bool x11_opt_in = gdk_backend && boost::starts_with(gdk_backend, "x11");
|
||||
|
||||
// NVIDIA's proprietary driver cannot back WebKitGTK's DMABUF renderer.
|
||||
// Force the shared-memory transport, which keeps compositing and threaded
|
||||
// scrolling; the second variable clears Debian/Ubuntu/openSUSE's isNVIDIA().
|
||||
if (::access("/proc/driver/nvidia/version", F_OK) == 0 && WebView::SupportsForceSharedMemory()) {
|
||||
::setenv("WEBKIT_DMABUF_RENDERER_FORCE_SHM", "1", /* replace */ false);
|
||||
::setenv("WEBKIT_FORCE_DMABUF_RENDERER", "1", /* replace */ false);
|
||||
}
|
||||
|
||||
if (x11_opt_in) {
|
||||
// ===== X11 / XWayland (opted in via GDK_BACKEND=x11) =====
|
||||
BOOST_LOG_TRIVIAL(info) << "GDK_BACKEND=x11 detected; applying v2.3.2 X11/XWayland workarounds.";
|
||||
@@ -1204,23 +1215,18 @@ int CLI::run(int argc, char **argv)
|
||||
if (wayland_env && *wayland_env) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Wayland detected but wxWidgets has no EGL support (wxHAS_EGL is OFF). Forcing X11 backend.";
|
||||
::setenv("GDK_BACKEND", "x11", true);
|
||||
|
||||
// GTK will use X11 in a Wayland session here, which is the
|
||||
// one default-path case that really runs through XWayland.
|
||||
// WebKit2GTK accelerated compositing could fail silently
|
||||
// there on releases before 2.42, so force it off. Disabling
|
||||
// it also turns off threaded scrolling, hence the gate.
|
||||
if (WebView::NeedsCompositingWorkaround())
|
||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// WebKit2GTK compositing can fail under XWayland on older
|
||||
// WebKit releases. Disable it only when both DISPLAY and
|
||||
// WAYLAND_DISPLAY are set (i.e. an XWayland session is in
|
||||
// use). On pure X11 or native Wayland, compositing is left
|
||||
// enabled so WebKit retains HW acceleration.
|
||||
{
|
||||
const char* display_env_wk = ::getenv("DISPLAY");
|
||||
const char* wayland_env_wk = ::getenv("WAYLAND_DISPLAY");
|
||||
if (display_env_wk && *display_env_wk && wayland_env_wk && *wayland_env_wk) {
|
||||
::setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", /* replace */ false);
|
||||
}
|
||||
}
|
||||
|
||||
// XInitThreads is needed before GStreamer may use Xlib. On
|
||||
// native Wayland without DISPLAY, GStreamer uses waylandsink
|
||||
// (no Xlib involved), so the call is skipped.
|
||||
|
||||
@@ -429,7 +429,8 @@ if [ "\$XDG_SESSION_TYPE" = "wayland" ] && [ "\$ZINK_DISABLE_OVERRIDE" != "1" ];
|
||||
export __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json
|
||||
export MESA_LOADER_DRIVER_OVERRIDE=zink
|
||||
export GALLIUM_DRIVER=zink
|
||||
export WEBKIT_DISABLE_DMABUF_RENDERER=1
|
||||
# WebKit's NVIDIA DMABUF workaround is applied by the binary itself,
|
||||
# gated on the runtime WebKitGTK version (src/OrcaSlicer.cpp).
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -327,6 +327,8 @@ set(lisbslic3r_sources
|
||||
Geometry/VoronoiUtils.cpp
|
||||
Geometry/VoronoiUtils.hpp
|
||||
Geometry/VoronoiVisualUtils.hpp
|
||||
ChromaStack.cpp
|
||||
ChromaStack.hpp
|
||||
IMEXArrange.cpp
|
||||
IMEXArrange.hpp
|
||||
IMEXHelpers.cpp
|
||||
|
||||
@@ -0,0 +1,394 @@
|
||||
#include "ChromaStack.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <cstdlib>
|
||||
#include <numeric>
|
||||
|
||||
#include <tbb/blocked_range.h>
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include <miniz.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// How many e-folding lengths of absorption count as "the colour beneath no longer shows". Three
|
||||
// passes 5% of the light, which is about where the eye stops seeing what is under a layer.
|
||||
static constexpr float OPAQUE_EFOLDS = 3.f;
|
||||
|
||||
// TD is a filament property, so a zero or negative value in a preset is a data error rather than a
|
||||
// request for an infinitely opaque filament. Clamp instead: below this the filament is opaque at a
|
||||
// single layer anyway.
|
||||
static constexpr float TD_MIN_MM = 0.05f;
|
||||
static constexpr float TD_MAX_MM = 100.f;
|
||||
|
||||
static float srgb_to_linear(float c)
|
||||
{
|
||||
return c <= 0.04045f ? c / 12.92f : std::pow((c + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
|
||||
static float linear_to_srgb(float c)
|
||||
{
|
||||
return c <= 0.0031308f ? c * 12.92f : 1.055f * std::pow(c, 1.f / 2.4f) - 0.055f;
|
||||
}
|
||||
|
||||
// CIELAB under D65, from non-linear sRGB in [0, 1]. Only used for nearest-colour search, so the
|
||||
// absolute white point matters far less than both sides using the same one.
|
||||
struct Lab
|
||||
{
|
||||
float l = 0.f, a = 0.f, b = 0.f;
|
||||
};
|
||||
|
||||
static Lab srgb_to_lab(float r, float g, float b)
|
||||
{
|
||||
const float rl = srgb_to_linear(r), gl = srgb_to_linear(g), bl = srgb_to_linear(b);
|
||||
const float x = (0.4124f * rl + 0.3576f * gl + 0.1805f * bl) / 0.95047f;
|
||||
const float y = (0.2126f * rl + 0.7152f * gl + 0.0722f * bl);
|
||||
const float z = (0.0193f * rl + 0.1192f * gl + 0.9505f * bl) / 1.08883f;
|
||||
auto f = [](float t) {
|
||||
return t > 0.008856f ? std::cbrt(t) : (7.787f * t + 16.f / 116.f);
|
||||
};
|
||||
const float fx = f(x), fy = f(y), fz = f(z);
|
||||
return Lab{ 116.f * fy - 16.f, 500.f * (fx - fy), 200.f * (fy - fz) };
|
||||
}
|
||||
|
||||
static float delta_e(const Lab &p, const Lab &q)
|
||||
{
|
||||
const float dl = p.l - q.l, da = p.a - q.a, db = p.b - q.b;
|
||||
return std::sqrt(dl * dl + da * da + db * db);
|
||||
}
|
||||
|
||||
int ChromaStack::relief_layers() const
|
||||
{
|
||||
int n = 0;
|
||||
for (const ChromaBand &band : bands)
|
||||
n += std::max(0, band.layers);
|
||||
return n;
|
||||
}
|
||||
|
||||
int ChromaStack::total_layers() const { return std::max(0, base_layers) + relief_layers(); }
|
||||
|
||||
float ChromaStack::depth_mm() const { return float(relief_layers()) * layer_height_mm; }
|
||||
|
||||
std::vector<ColorRGB> chroma_palette(const ChromaStack &stack)
|
||||
{
|
||||
const int total = stack.total_layers();
|
||||
// No bands means no picture and no filament to print the base in, whatever base_layers says.
|
||||
if (total <= 0 || stack.bands.empty() || stack.layer_height_mm <= 0.f)
|
||||
return {};
|
||||
|
||||
// The filament each printed layer is in, bottom-up: the base first, in the bottom band's filament,
|
||||
// then the bands themselves.
|
||||
std::vector<const ChromaFilament *> per_layer;
|
||||
per_layer.reserve(size_t(total));
|
||||
for (int i = 0; i < std::max(0, stack.base_layers); ++i)
|
||||
per_layer.push_back(&stack.bands.front().filament);
|
||||
for (const ChromaBand &band : stack.bands)
|
||||
for (int i = 0; i < band.layers; ++i)
|
||||
per_layer.push_back(&band.filament);
|
||||
|
||||
std::vector<ColorRGB> palette(size_t(total) + 1);
|
||||
|
||||
// Linear light, walking up from an opaque base: C_0 is the bottom layer's own colour, so a
|
||||
// column one layer tall comes out exactly that colour however translucent the filament is.
|
||||
std::array<float, 3> acc{ srgb_to_linear(per_layer.front()->color.r()), srgb_to_linear(per_layer.front()->color.g()),
|
||||
srgb_to_linear(per_layer.front()->color.b()) };
|
||||
auto store = [&palette](size_t k, const std::array<float, 3> &lin) {
|
||||
palette[k] = ColorRGB(std::clamp(linear_to_srgb(lin[0]), 0.f, 1.f), std::clamp(linear_to_srgb(lin[1]), 0.f, 1.f),
|
||||
std::clamp(linear_to_srgb(lin[2]), 0.f, 1.f));
|
||||
};
|
||||
store(0, acc);
|
||||
|
||||
for (int k = 1; k <= total; ++k) {
|
||||
const ChromaFilament &f = *per_layer[size_t(k - 1)];
|
||||
const float td = std::clamp(f.td_mm, TD_MIN_MM, TD_MAX_MM);
|
||||
// The depth at which this filament hides what is behind it, turned into the e-folding length
|
||||
// of its absorption; see CHROMA_TD_OPAQUE_FRACTION.
|
||||
const float efold = CHROMA_TD_OPAQUE_FRACTION * td / OPAQUE_EFOLDS;
|
||||
const float t = std::exp(-stack.layer_height_mm / efold);
|
||||
const std::array<float, 3> own{ srgb_to_linear(f.color.r()), srgb_to_linear(f.color.g()),
|
||||
srgb_to_linear(f.color.b()) };
|
||||
for (int c = 0; c < 3; ++c)
|
||||
acc[size_t(c)] = (1.f - t) * own[size_t(c)] + t * acc[size_t(c)];
|
||||
store(size_t(k), acc);
|
||||
}
|
||||
return palette;
|
||||
}
|
||||
|
||||
ChromaSolve chroma_solve(const uint8_t *rgb, int width, int height, const ChromaStack &stack)
|
||||
{
|
||||
ChromaSolve solve;
|
||||
const int total = stack.total_layers();
|
||||
const int base = std::max(0, stack.base_layers);
|
||||
const int relief = stack.relief_layers();
|
||||
if (rgb == nullptr || width <= 0 || height <= 0 || relief <= 0 || relief > CHROMA_MAX_RELIEF_LAYERS)
|
||||
return solve;
|
||||
|
||||
const std::vector<ColorRGB> palette = chroma_palette(stack);
|
||||
if (int(palette.size()) != total + 1)
|
||||
return solve;
|
||||
|
||||
// Only the heights the solver may choose: base (nothing of the relief printed) up to total.
|
||||
const int choices = relief + 1;
|
||||
const size_t choice_count = size_t(choices);
|
||||
std::vector<Lab> choice_lab(choice_count);
|
||||
std::vector<std::array<uint8_t, 3>> choice_rgb(choice_count);
|
||||
for (int i = 0; i < choices; ++i) {
|
||||
const ColorRGB &c = palette[size_t(base + i)];
|
||||
choice_lab[size_t(i)] = srgb_to_lab(c.r(), c.g(), c.b());
|
||||
choice_rgb[size_t(i)] = { uint8_t(std::lround(c.r() * 255.f)), uint8_t(std::lround(c.g() * 255.f)),
|
||||
uint8_t(std::lround(c.b() * 255.f)) };
|
||||
}
|
||||
|
||||
const size_t count = size_t(width) * size_t(height);
|
||||
solve.width = width;
|
||||
solve.height = height;
|
||||
solve.levels.resize(count);
|
||||
solve.grey.resize(count);
|
||||
solve.preview.resize(count * 3);
|
||||
|
||||
std::vector<float> errors(count, 0.f);
|
||||
|
||||
auto pick = [&](float r, float g, float b, float &out_error) {
|
||||
const Lab target = srgb_to_lab(std::clamp(r, 0.f, 1.f), std::clamp(g, 0.f, 1.f), std::clamp(b, 0.f, 1.f));
|
||||
int best = 0;
|
||||
float best_d = std::numeric_limits<float>::max();
|
||||
for (int i = 0; i < choices; ++i) {
|
||||
const float d = delta_e(target, choice_lab[size_t(i)]);
|
||||
if (d < best_d) {
|
||||
best_d = d;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
out_error = best_d;
|
||||
return best;
|
||||
};
|
||||
|
||||
// The grey level a chosen height is written as. Rounded to the nearest of 256 levels, which for
|
||||
// any practical layer budget (a few dozen) maps every height to its own level exactly - the
|
||||
// relief's steps stay on layer boundaries, which is the whole point of quantizing here.
|
||||
auto grey_of = [relief](int choice) {
|
||||
return uint8_t(std::lround(255.0 * double(choice) / double(relief)));
|
||||
};
|
||||
|
||||
auto write = [&](size_t px, int choice, float err) {
|
||||
solve.levels[px] = uint16_t(base + choice);
|
||||
solve.grey[px] = grey_of(choice);
|
||||
solve.preview[px * 3] = choice_rgb[size_t(choice)][0];
|
||||
solve.preview[px * 3 + 1] = choice_rgb[size_t(choice)][1];
|
||||
solve.preview[px * 3 + 2] = choice_rgb[size_t(choice)][2];
|
||||
errors[px] = err;
|
||||
};
|
||||
|
||||
if (stack.dither) {
|
||||
// Floyd-Steinberg, serpentine so the error does not drift consistently to one side. The
|
||||
// diffusion buffer holds the two rows the kernel reaches, in sRGB - which is where the
|
||||
// artifact being traded away lives, and what every reference implementation diffuses in.
|
||||
const size_t row_len = size_t(width);
|
||||
std::vector<std::array<float, 3>> row(row_len), next(row_len);
|
||||
for (int x = 0; x < width; ++x) {
|
||||
const uint8_t *src = rgb + size_t(x) * 3;
|
||||
row[size_t(x)] = { src[0] / 255.f, src[1] / 255.f, src[2] / 255.f };
|
||||
}
|
||||
for (int y = 0; y < height; ++y) {
|
||||
if (y + 1 < height)
|
||||
for (int x = 0; x < width; ++x) {
|
||||
const uint8_t *src = rgb + (size_t(y + 1) * size_t(width) + size_t(x)) * 3;
|
||||
next[size_t(x)] = { src[0] / 255.f, src[1] / 255.f, src[2] / 255.f };
|
||||
}
|
||||
const bool ltr = (y % 2) == 0;
|
||||
for (int i = 0; i < width; ++i) {
|
||||
const int x = ltr ? i : width - 1 - i;
|
||||
const size_t px = size_t(y) * size_t(width) + size_t(x);
|
||||
float err = 0.f;
|
||||
const int choice = pick(row[size_t(x)][0], row[size_t(x)][1], row[size_t(x)][2], err);
|
||||
write(px, choice, err);
|
||||
|
||||
std::array<float, 3> residual{};
|
||||
for (int c = 0; c < 3; ++c)
|
||||
residual[size_t(c)] = row[size_t(x)][size_t(c)] - choice_rgb[size_t(choice)][size_t(c)] / 255.f;
|
||||
|
||||
const int ahead = ltr ? x + 1 : x - 1;
|
||||
const int back = ltr ? x - 1 : x + 1;
|
||||
auto spread = [&](std::vector<std::array<float, 3>> &dst, int at, float w) {
|
||||
if (at < 0 || at >= width)
|
||||
return;
|
||||
for (int c = 0; c < 3; ++c)
|
||||
dst[size_t(at)][size_t(c)] += residual[size_t(c)] * w;
|
||||
};
|
||||
spread(row, ahead, 7.f / 16.f);
|
||||
if (y + 1 < height) {
|
||||
spread(next, back, 3.f / 16.f);
|
||||
spread(next, x, 5.f / 16.f);
|
||||
spread(next, ahead, 1.f / 16.f);
|
||||
}
|
||||
}
|
||||
row.swap(next);
|
||||
}
|
||||
} else {
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, height), [&](const tbb::blocked_range<int> &range) {
|
||||
for (int y = range.begin(); y < range.end(); ++y)
|
||||
for (int x = 0; x < width; ++x) {
|
||||
const size_t px = size_t(y) * size_t(width) + size_t(x);
|
||||
const uint8_t *src = rgb + px * 3;
|
||||
float err = 0.f;
|
||||
write(px, pick(src[0] / 255.f, src[1] / 255.f, src[2] / 255.f, err), err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
solve.mean_delta_e = count == 0 ? 0. : std::accumulate(errors.begin(), errors.end(), 0.) / double(count);
|
||||
solve.max_delta_e = errors.empty() ? 0. : double(*std::max_element(errors.begin(), errors.end()));
|
||||
return solve;
|
||||
}
|
||||
|
||||
float chroma_layers_to_opaque(float td_mm, float layer_height_mm)
|
||||
{
|
||||
if (layer_height_mm <= 0.f)
|
||||
return 0.f;
|
||||
return CHROMA_TD_OPAQUE_FRACTION * std::clamp(td_mm, TD_MIN_MM, TD_MAX_MM) / layer_height_mm;
|
||||
}
|
||||
|
||||
// Below about one dE two colours are the same colour to the eye. Half of that is the threshold for
|
||||
// "this layer changed something", so a step has to be comfortably invisible before a layer is called
|
||||
// wasted.
|
||||
static constexpr float VISIBLE_DE = 0.5f;
|
||||
|
||||
static float step_delta_e(const ColorRGB &a, const ColorRGB &b)
|
||||
{
|
||||
return delta_e(srgb_to_lab(a.r(), a.g(), a.b()), srgb_to_lab(b.r(), b.g(), b.b()));
|
||||
}
|
||||
|
||||
int chroma_distinct_levels(const ChromaStack &stack)
|
||||
{
|
||||
const std::vector<ColorRGB> palette = chroma_palette(stack);
|
||||
const int base = std::max(0, stack.base_layers);
|
||||
if (int(palette.size()) <= base + 1)
|
||||
return 0;
|
||||
int n = 0;
|
||||
for (size_t i = size_t(base) + 1; i < palette.size(); ++i)
|
||||
if (step_delta_e(palette[i - 1], palette[i]) > VISIBLE_DE)
|
||||
++n;
|
||||
return n;
|
||||
}
|
||||
|
||||
void chroma_trim_bands(ChromaStack &stack)
|
||||
{
|
||||
// Walked over the stack's own palette rather than over the filaments' TD, because how fast a band
|
||||
// runs out of anything to say depends on what is under it as much as on the filament. A light band
|
||||
// over a dark one is finished in four or five layers; a dark one over a light one goes on paying
|
||||
// for many more, long past the depth at which it is nominally opaque, because the last few percent
|
||||
// of light coming through still moves a near-black colour perceptibly.
|
||||
const std::vector<ColorRGB> palette = chroma_palette(stack);
|
||||
if (palette.empty())
|
||||
return;
|
||||
int layer = std::max(0, stack.base_layers); // palette index of the layer under the band being looked at
|
||||
for (ChromaBand &band : stack.bands) {
|
||||
int keep = 1;
|
||||
for (int i = 1; i <= band.layers; ++i) {
|
||||
const size_t cur = size_t(layer) + size_t(i);
|
||||
if (cur >= palette.size())
|
||||
break;
|
||||
if (step_delta_e(palette[cur - 1], palette[cur]) > VISIBLE_DE)
|
||||
keep = i; // the last layer of this band that still changes the colour
|
||||
}
|
||||
layer += band.layers;
|
||||
band.layers = std::clamp(keep, 1, band.layers);
|
||||
}
|
||||
}
|
||||
|
||||
int chroma_level_at(const ChromaStack &stack, double z_mm)
|
||||
{
|
||||
const int total = stack.total_layers();
|
||||
if (total <= 0 || stack.layer_height_mm <= 0.f)
|
||||
return 0;
|
||||
// Layer k occupies ((k-1)h, k h], so a point inside it rounds up. The epsilon keeps a surface
|
||||
// sitting exactly on a layer boundary in the layer that printed it rather than in the one above,
|
||||
// which is the difference between a terrace showing its own colour and showing the next band's.
|
||||
const int k = int(std::ceil(z_mm / double(stack.layer_height_mm) - 1e-6));
|
||||
return std::clamp(k, 1, total);
|
||||
}
|
||||
|
||||
std::vector<unsigned char> chroma_encode_height_png(const ChromaSolve &solve)
|
||||
{
|
||||
if (solve.empty())
|
||||
return {};
|
||||
size_t size = 0;
|
||||
void *png = tdefl_write_image_to_png_file_in_memory_ex(solve.grey.data(), solve.width, solve.height, 1, &size,
|
||||
MZ_DEFAULT_LEVEL, MZ_FALSE);
|
||||
if (png == nullptr || size == 0)
|
||||
return {};
|
||||
const unsigned char *bytes = static_cast<const unsigned char *>(png);
|
||||
std::vector<unsigned char> out(bytes, bytes + size);
|
||||
mz_free(png);
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<ChromaRange> chroma_ranges(const ChromaStack &stack, double z_bottom)
|
||||
{
|
||||
std::vector<ChromaRange> out;
|
||||
if (stack.layer_height_mm <= 0.f)
|
||||
return out;
|
||||
// The layer height is stored as a float, like every other dimension of a texture displacement
|
||||
// layer, so multiplying it up leaves noise in the last few digits - 5 layers of 0.1 mm come out as
|
||||
// 0.5000000074. The slicer derives its own layer boundaries from the print config in double
|
||||
// precision and lands on 0.5 exactly, and a boundary that misses it by any amount is a boundary a
|
||||
// layer can fall on the wrong side of. Rounding to the nanometre removes the noise without
|
||||
// disturbing any layer height a profile can express.
|
||||
const auto snap = [](double z) { return std::round(z * 1e6) / 1e6; };
|
||||
|
||||
// The base is printed in the bottom band's filament - it is what is loaded when the print starts -
|
||||
// so it extends that band's range down to the first layer instead of being a swap of its own.
|
||||
int layer = std::max(0, stack.base_layers);
|
||||
bool first = true;
|
||||
for (const ChromaBand &band : stack.bands) {
|
||||
if (band.layers <= 0)
|
||||
continue;
|
||||
const double z_min = snap(z_bottom + (first ? 0. : double(layer) * double(stack.layer_height_mm)));
|
||||
first = false;
|
||||
layer += band.layers;
|
||||
const double z_max = snap(z_bottom + double(layer) * double(stack.layer_height_mm));
|
||||
if (!out.empty() && out.back().filament == band.filament.filament)
|
||||
out.back().z_max = z_max; // no swap happens here, so it is one range
|
||||
else
|
||||
out.push_back(ChromaRange{ z_min, z_max, band.filament.filament });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
ChromaStack chroma_default_stack(const std::vector<ChromaFilament> &filaments, float layer_height_mm,
|
||||
int base_layers, int relief_layers)
|
||||
{
|
||||
ChromaStack stack;
|
||||
stack.layer_height_mm = layer_height_mm > 0.f ? layer_height_mm : 0.08f;
|
||||
stack.base_layers = std::max(0, base_layers);
|
||||
if (filaments.empty() || relief_layers <= 0)
|
||||
return stack;
|
||||
|
||||
std::vector<ChromaFilament> ordered = filaments;
|
||||
std::stable_sort(ordered.begin(), ordered.end(), [](const ChromaFilament &a, const ChromaFilament &b) {
|
||||
// Relative luminance of the linear colour: the perceptual order, so a saturated yellow
|
||||
// counts as light and a saturated blue as dark, which a plain r+g+b sum gets wrong.
|
||||
auto lum = [](const ColorRGB &c) {
|
||||
return 0.2126f * srgb_to_linear(c.r()) + 0.7152f * srgb_to_linear(c.g()) + 0.0722f * srgb_to_linear(c.b());
|
||||
};
|
||||
return lum(a.color) > lum(b.color);
|
||||
});
|
||||
|
||||
const int count = int(ordered.size());
|
||||
int left = relief_layers;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
// Spread the remainder over the first bands rather than piling it onto the last one.
|
||||
const int share = left / (count - i) + ((left % (count - i)) > 0 ? 1 : 0);
|
||||
ChromaBand band;
|
||||
band.filament = ordered[size_t(i)];
|
||||
band.layers = std::max(1, share);
|
||||
left -= share;
|
||||
stack.bands.push_back(band);
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -0,0 +1,286 @@
|
||||
#ifndef slic3r_ChromaStack_hpp_
|
||||
#define slic3r_ChromaStack_hpp_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
||||
#include "Color.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// ChromaStack-style colour printing: a picture is reproduced on a single face by stacking thin layers
|
||||
// of a few filaments, swapping filament at fixed layer boundaries, and varying the *height* of each
|
||||
// pixel's column. Where the column is low, the layers above it are missing and the filament below
|
||||
// shows; where it is high, the topmost filament covers everything beneath it. Because filament is
|
||||
// translucent, a column that stops part-way through a run shows a blend of that run's filament and
|
||||
// what is under it, which is what gives far more colours than there are filaments loaded.
|
||||
//
|
||||
// So there are only two things to compute, and they are both here:
|
||||
// - the *palette*: the colour the stack shows at every possible column height (chroma_palette());
|
||||
// - the *height map*: per pixel, the column height whose palette colour is closest to the picture
|
||||
// (chroma_solve()).
|
||||
//
|
||||
// Everything else is machinery that already exists. The height map is handed to a texture
|
||||
// displacement layer as its height texture, so the existing projection/preview/bake pipeline turns
|
||||
// it into real geometry (see TextureDisplacement.hpp), and the filament swaps are handed to the
|
||||
// object's height ranges (ModelObject::layer_config_ranges), which is the slicer's own way of
|
||||
// saying "print this Z band with that filament".
|
||||
|
||||
// The depth of filament that blocks essentially all the light behind it, as a fraction of the
|
||||
// filament's transmission distance (TD).
|
||||
//
|
||||
// TD's scale is empirical, not physical: its values run from about 1
|
||||
// for an opaque filament to 10 and above for a nearly clear one, and the community's calibration
|
||||
// prints (the "seashell" step test) read a TD off the step where the colour beneath stops showing.
|
||||
// That step is about a tenth of the TD number, which is the only thing this constant encodes - a
|
||||
// filament of TD 2 stops hiding what is under it at roughly 0.2 mm, i.e. after two or three layers,
|
||||
// while one of TD 8 needs most of a millimetre. The model below turns that into a per-layer
|
||||
// transmittance; see chroma_palette().
|
||||
static constexpr float CHROMA_TD_OPAQUE_FRACTION = 0.1f;
|
||||
|
||||
// What a fresh stack gets above the base. Twenty-four layers is about two millimetres at a fine layer
|
||||
// height: enough steps for a continuous tonal ramp, and still a print that finishes in an evening.
|
||||
static constexpr int CHROMA_DEFAULT_RELIEF_LAYERS = 24;
|
||||
|
||||
// The most picture layers the solve can express. A column's height above the base is stored as one
|
||||
// byte per pixel in the 8-bit grayscale height map the displacement reads, so beyond this two
|
||||
// different heights would have to share a grey level and the relief's steps would stop landing on
|
||||
// layer boundaries - which is the one thing the quantization exists to guarantee.
|
||||
//
|
||||
// It bounds the bands, not the stack: the base is whatever the model already is under the painted
|
||||
// face, often hundreds of layers on a tall part, and nothing about it is quantized into the height
|
||||
// map. As a limit on the picture it is not one in practice - 255 layers is 20 mm of relief at
|
||||
// 0.08 mm, far past where a picture gains anything.
|
||||
static constexpr int CHROMA_MAX_RELIEF_LAYERS = 255;
|
||||
|
||||
// The most filament changes one stack may hold. Every band is a swap the printer has to make, so this is
|
||||
// a limit on patience rather than on the model; the palette itself is bounded by the layer count.
|
||||
static constexpr size_t CHROMA_MAX_BANDS = 16;
|
||||
|
||||
// One filament as the colour model sees it: what colour it is, and how far light gets into it.
|
||||
struct ChromaFilament
|
||||
{
|
||||
// 0-based index into the project's filaments, which is what a height range's `extruder` option
|
||||
// (1-based) is written from. -1 for a filament that is not loaded, which the GUI refuses to
|
||||
// solve with rather than silently printing in the wrong colour.
|
||||
int filament = -1;
|
||||
ColorRGB color;
|
||||
// The filament preset's own name, so the panel can say which filament this is. Six similar greys
|
||||
// are six identical swatches and six identical numbers otherwise. Stored rather than looked up on
|
||||
// demand because a stack outlives the project's filament list - a preset can be swapped out from
|
||||
// under a saved stack, and then the name is the only record of what it was solved against.
|
||||
std::string name;
|
||||
// Transmission distance, mm, as the filament preset reports it
|
||||
// (`filament_transmission_distance`). Clamped to a sane range when the palette is built: zero
|
||||
// would make a layer infinitely opaque and a negative value is meaningless.
|
||||
float td_mm = 4.f;
|
||||
|
||||
template<class Archive> void serialize(Archive &ar)
|
||||
{
|
||||
float r = color.r(), g = color.g(), b = color.b();
|
||||
ar(filament, r, g, b, td_mm, name);
|
||||
color = ColorRGB(r, g, b);
|
||||
}
|
||||
};
|
||||
|
||||
// A run of consecutive layers printed in one filament. The stack is a list of these, bottom-up, so
|
||||
// a band boundary is exactly where the print pauses and the filament is changed.
|
||||
struct ChromaBand
|
||||
{
|
||||
ChromaFilament filament;
|
||||
int layers = 1;
|
||||
|
||||
template<class Archive> void serialize(Archive &ar) { ar(filament, layers); }
|
||||
};
|
||||
|
||||
// The whole recipe: which filaments, in which order, how thick a layer is, and how much of the
|
||||
// bottom is solid backing rather than picture.
|
||||
struct ChromaStack
|
||||
{
|
||||
std::vector<ChromaBand> bands; // bottom-up; bands.front() is the one the base is printed in
|
||||
|
||||
// The print's layer height, mm. Taken from the print profile rather than chosen here - a stack
|
||||
// solved against 0.08 mm layers prints as the wrong picture at 0.2 mm, so the GUI re-solves when
|
||||
// the profile's layer height changes.
|
||||
float layer_height_mm = 0.08f;
|
||||
|
||||
// Layers of solid backing under the picture: everything the model already is below the painted
|
||||
// face. Not part of any band - the bands are the picture - but printed in the bottom band's
|
||||
// filament, since that is what is loaded when the print starts.
|
||||
//
|
||||
// Keeping it out of the bands is what lets it be what it really is: a measurement of the model,
|
||||
// often a hundred layers or more, while a band is a handful. Folding the two together put a
|
||||
// three-digit number on a slider meant for single digits and made "how thick is the backing" and
|
||||
// "how many layers does the first filament get" the same control, which they are not.
|
||||
//
|
||||
// The colour model treats the base as opaque. That is what makes a column's colour depend only on
|
||||
// the layers above it, and why the panel warns about a base thin enough to let the plate show
|
||||
// through.
|
||||
int base_layers = 4;
|
||||
|
||||
// Whether `base_layers` follows the model or was typed in. It follows by default, and it should:
|
||||
// the painted face is wherever the geometry puts it, and a base that disagrees with it shifts
|
||||
// every colour in the picture - the stack ends up anchored at the bottom of the part instead of
|
||||
// under the face, so the picture's whole range is squeezed into the first few millimetres and
|
||||
// everything above it clamps to the top band.
|
||||
//
|
||||
// It cannot be measured once and left: a layer is created, and its picture chosen, before there
|
||||
// is any paint to measure, and the face moves again whenever the part is scaled or the layer
|
||||
// height changes. So the panel re-measures it every frame while this is set.
|
||||
bool base_auto = true;
|
||||
|
||||
// Spread the quantization error into neighbouring pixels (Floyd-Steinberg, serpentine) instead
|
||||
// of rounding each pixel to its nearest palette colour on its own. A palette of twenty or thirty
|
||||
// colours has to cover a photograph's whole range, so plain rounding leaves visible bands across
|
||||
// a sky or a cheek; diffusing the error trades them for fine stipple, which at a tenth of a
|
||||
// millimetre per pixel reads as the intermediate colour. Off for flat-coloured artwork, where
|
||||
// stipple is the artifact rather than the cure.
|
||||
bool dither = false;
|
||||
|
||||
// Total printed layers, base included: what the tallest column is.
|
||||
int total_layers() const;
|
||||
// Layers the picture is built from, i.e. the bands' own total above the base.
|
||||
int relief_layers() const;
|
||||
// The relief's height in mm, which is the `depth_mm` of the texture displacement layer the
|
||||
// height map is handed to.
|
||||
float depth_mm() const;
|
||||
bool empty() const { return bands.empty(); }
|
||||
|
||||
template<class Archive> void serialize(Archive &ar) { ar(bands, layer_height_mm, base_layers, dither, base_auto); }
|
||||
};
|
||||
|
||||
// The colour the stack shows at every column height: entry k is a column of exactly k printed
|
||||
// layers, so the vector has total_layers() + 1 entries.
|
||||
//
|
||||
// The model is Beer-Lambert absorption, layer by layer. A layer of thickness h in a filament of
|
||||
// transmission distance TD passes
|
||||
//
|
||||
// T = exp(-h / (CHROMA_TD_OPAQUE_FRACTION * TD / 3))
|
||||
//
|
||||
// of the light coming from under it and shows its own colour with the rest, so
|
||||
//
|
||||
// C_k = (1 - T_k) * c_k + T_k * C_(k-1)
|
||||
//
|
||||
// walking up from an opaque base, C_0 = c_1. The divisor is what makes "opaque at
|
||||
// CHROMA_TD_OPAQUE_FRACTION * TD" true: three e-folding lengths pass 5% of the light, which is
|
||||
// where the colour beneath stops being visible to the eye.
|
||||
//
|
||||
// Beer-Lambert is not just the physically honest choice here, it is the only one that stays
|
||||
// consistent when applied per layer: transmittance multiplies, so a run of n layers of one filament
|
||||
// passes exp(-n h / d) whether it is composited in one step or n, and the palette does not depend on
|
||||
// how the stack happens to be cut into bands.
|
||||
//
|
||||
// Compositing is done on linear light and the entries are converted back to sRGB, because the
|
||||
// quantity being attenuated is light, not an sRGB code.
|
||||
std::vector<ColorRGB> chroma_palette(const ChromaStack &stack);
|
||||
|
||||
// What chroma_solve() produced.
|
||||
struct ChromaSolve
|
||||
{
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
// Per pixel, row-major and top-to-bottom like DecodedHeightTexture: the column's printed layer
|
||||
// count, between base_layers and total_layers(). Sixteen bits because it counts from the build
|
||||
// plate, and the base alone can be several hundred layers on a tall part.
|
||||
std::vector<uint16_t> levels;
|
||||
// The same thing as the 8-bit grayscale height map the displacement samples: 0 at base_layers,
|
||||
// 255 at total_layers(). This is what gets encoded as a PNG into the layer's `image_data`, which
|
||||
// is why the solve has to be the thing that owns the quantization - the relief's steps have to
|
||||
// land on layer boundaries, and a grey level is the only channel the displacement reads.
|
||||
std::vector<uint8_t> grey;
|
||||
// Per pixel RGB: the palette colour the solve chose, i.e. what the print is predicted to look
|
||||
// like. Shown in the panel next to the source picture, since the 3D view can only show the
|
||||
// relief - the colours do not exist until the print swaps filament.
|
||||
std::vector<uint8_t> preview;
|
||||
|
||||
// How far the prediction landed from the picture, as a mean and worst-case CIE76 dE over all
|
||||
// pixels. The panel reports it because it is the only honest answer to "will this stack
|
||||
// reproduce my image": under about 5 is a good match, over about 15 means the filaments cannot
|
||||
// reach the picture's colours and no amount of layers will fix it.
|
||||
double mean_delta_e = 0.;
|
||||
double max_delta_e = 0.;
|
||||
|
||||
bool empty() const { return width <= 0 || height <= 0 || levels.empty(); }
|
||||
};
|
||||
|
||||
// Solves `rgb` (width * height * 3 bytes, row-major, top-to-bottom) against the stack's palette.
|
||||
// An empty stack, a zero-sized image or a stack with no relief layers gives an empty result.
|
||||
ChromaSolve chroma_solve(const uint8_t *rgb, int width, int height, const ChromaStack &stack);
|
||||
|
||||
// How many layers of this filament it takes to hide what is under it, at this layer height. The
|
||||
// single most useful number about a stack and the one nothing else makes visible: it is what decides
|
||||
// whether a band is a gradient or a step.
|
||||
//
|
||||
// A filament hides what is behind it at about CHROMA_TD_OPAQUE_FRACTION of its TD (see
|
||||
// chroma_palette()), so this is that depth in layers. Below two the band has no intermediate colours
|
||||
// at all - the picture can only use the filament or not use it - which is what a coarse layer height
|
||||
// does to an otherwise good stack: TD 4 at 0.2 mm covers in two layers, the same filament at 0.08 mm
|
||||
// in five.
|
||||
float chroma_layers_to_opaque(float td_mm, float layer_height_mm);
|
||||
|
||||
// How many of the picture's layers actually change the colour.
|
||||
//
|
||||
// A band eventually stops saying anything: once it hides what is under it, another layer of it shows
|
||||
// the colour the one below already showed. Those layers still cost print time and still take up
|
||||
// relief height, and nothing about the stack makes them visible - the band looks like ten layers of
|
||||
// gradient and is, say, five of gradient and five of flat.
|
||||
//
|
||||
// Measured against the palette rather than against TD, because how long a band keeps paying depends
|
||||
// on what is under it as much as on the filament: a light band over a dark one is finished in four or
|
||||
// five layers, while a dark one over a light one goes on moving the colour perceptibly well past the
|
||||
// depth at which it is nominally opaque.
|
||||
int chroma_distinct_levels(const ChromaStack &stack);
|
||||
|
||||
// Shortens every band to its last layer that still changes the colour. Bands already shorter than
|
||||
// that are left alone: they are still inside their fade, where every layer counts.
|
||||
void chroma_trim_bands(ChromaStack &stack);
|
||||
|
||||
// The printed layer a point `z_mm` above the bottom of the object falls in: 1-based, clamped to the
|
||||
// stack, 0 for a stack that prints nothing. Pair it with chroma_palette() to get the colour shown
|
||||
// there.
|
||||
//
|
||||
// This, rather than the height map, is what decides the colour a surface shows. A filament swap
|
||||
// happens at one print Z across the whole plate, so every surface crossing that height changes colour
|
||||
// whatever the picture says - the terraces of the relief, the walls between them, and the sides of the
|
||||
// model alike. Colouring a preview this way is therefore not an approximation of the print; it is what
|
||||
// the print does, and it keeps working after the relief has been baked into the mesh and the height
|
||||
// map is no longer attached to anything on screen.
|
||||
int chroma_level_at(const ChromaStack &stack, double z_mm);
|
||||
|
||||
// The solve's height map as an 8-bit grayscale PNG, ready to be stored in a
|
||||
// TextureDisplacementLayer's `image_data`.
|
||||
std::vector<unsigned char> chroma_encode_height_png(const ChromaSolve &solve);
|
||||
|
||||
// One filament swap as an object height range: the Z band, in the object's own coordinates (0 at its
|
||||
// bottom), that prints in `filament` (0-based).
|
||||
struct ChromaRange
|
||||
{
|
||||
double z_min = 0.;
|
||||
double z_max = 0.;
|
||||
int filament = -1;
|
||||
};
|
||||
|
||||
// The stack's bands as height ranges, in object coordinates, starting at `z_bottom` (which is 0 for
|
||||
// a stack whose base starts at the object's first layer). Bands printed in the same filament as the
|
||||
// band below are merged, so a stack that repeats a filament does not produce a pointless swap.
|
||||
std::vector<ChromaRange> chroma_ranges(const ChromaStack &stack, double z_bottom = 0.);
|
||||
|
||||
// A starting point for a stack: the given filaments, lightest at the bottom, each with an equal
|
||||
// share of `relief_layers` on top of `base_layers`.
|
||||
//
|
||||
// Light-to-dark is the convention filament-painting guides teach, and it is not arbitrary: a dark
|
||||
// filament over a light one reads as a midtone at one or two layers and as the dark colour at four,
|
||||
// so putting the dark filaments last is what buys the long tonal ramp. The reverse order gives a few
|
||||
// washed-out steps and then nothing.
|
||||
ChromaStack chroma_default_stack(const std::vector<ChromaFilament> &filaments, float layer_height_mm,
|
||||
int base_layers, int relief_layers);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_ChromaStack_hpp_
|
||||
+27
-18
@@ -432,27 +432,36 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
|
||||
|
||||
std::string OozePrevention::pre_toolchange(GCode& gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
unsigned int extruder_id = gcodegen.writer().filament()->id();
|
||||
const auto& filament_idle_temp = gcodegen.config().idle_temperature;
|
||||
if (filament_idle_temp.get_at(extruder_id) == 0) {
|
||||
// There is no idle temperature defined in filament settings.
|
||||
// Use the delta value from print config.
|
||||
if (gcodegen.config().standby_temperature_delta.value != 0) {
|
||||
// we assume that heating is always slower than cooling, so no need to block
|
||||
gcode += gcodegen.writer().set_temperature
|
||||
(this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, extruder_id);
|
||||
gcode.pop_back();
|
||||
gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed
|
||||
}
|
||||
} else {
|
||||
// Use the value from filament settings. That one is absolute, not delta.
|
||||
gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id);
|
||||
gcode.pop_back();
|
||||
gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed
|
||||
|
||||
// Check if this tool will ever be used again in this print.
|
||||
bool is_last_use = false;
|
||||
if (gcodegen.m_print != nullptr && !gcodegen.m_print->tool_ordering().empty()) {
|
||||
coordf_t current_z = gcodegen.layer() ? gcodegen.layer()->print_z : gcodegen.writer().get_position().z();
|
||||
const auto& tool_ordering = gcodegen.m_print->tool_ordering();
|
||||
const auto& cfg = gcodegen.config();
|
||||
const int heater = imex_physical_heater_for(cfg.is_imex.value, cfg.physical_extruder_map, int(extruder_id));
|
||||
is_last_use = tool_ordering.is_last_extrusion_layer(current_z, extruder_id);
|
||||
for (unsigned int f : tool_ordering.all_extruders())
|
||||
if (is_last_use && imex_physical_heater_for(cfg.is_imex.value, cfg.physical_extruder_map, int(f)) == heater)
|
||||
is_last_use = tool_ordering.is_last_extrusion_layer(current_z, f);
|
||||
}
|
||||
|
||||
int temp = 0;
|
||||
if (!is_last_use) {
|
||||
const auto& filament_idle_temp = gcodegen.config().idle_temperature;
|
||||
if (filament_idle_temp.get_at(extruder_id) == 0) {
|
||||
if (gcodegen.config().standby_temperature_delta.value == 0)
|
||||
return std::string();
|
||||
temp = this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value;
|
||||
} else {
|
||||
temp = filament_idle_temp.get_at(extruder_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::string gcode = gcodegen.writer().set_temperature(temp, false, extruder_id);
|
||||
gcode.pop_back();
|
||||
gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed
|
||||
return gcode;
|
||||
}
|
||||
|
||||
|
||||
@@ -1004,6 +1004,7 @@ protected:
|
||||
size_t num_objects,
|
||||
size_t num_islands);
|
||||
|
||||
friend class OozePrevention;
|
||||
friend class Wipe;
|
||||
friend class WipeTowerIntegration;
|
||||
friend class PressureEqualizer;
|
||||
|
||||
@@ -1276,6 +1276,13 @@ void ToolOrdering::collect_extruder_statistics(bool prime_multi_material)
|
||||
sort_remove_duplicates(m_all_printing_extruders);
|
||||
}
|
||||
|
||||
m_last_layer_per_extruder.clear();
|
||||
for (const LayerTools& lt : m_layer_tools) {
|
||||
for (unsigned int ext : lt.extruders) {
|
||||
m_last_layer_per_extruder[ext] = lt.print_z;
|
||||
}
|
||||
}
|
||||
|
||||
if (prime_multi_material && ! m_all_printing_extruders.empty()) {
|
||||
// Reorder m_all_printing_extruders in the sequence they will be primed, the last one will be m_first_printing_extruder.
|
||||
// Then set m_first_printing_extruder to the 1st extruder primed.
|
||||
@@ -3664,5 +3671,11 @@ int WipingExtrusions::get_support_interface_extruder_overrides(const PrintObject
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
bool ToolOrdering::is_last_extrusion_layer(coordf_t print_z, unsigned int extruder_id) const
|
||||
{
|
||||
auto it = m_last_layer_per_extruder.find(extruder_id);
|
||||
if (it == m_last_layer_per_extruder.end())
|
||||
return true;
|
||||
return print_z >= it->second - EPSILON;
|
||||
}
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -370,6 +370,7 @@ public:
|
||||
std::vector<int> & initial_filaments);
|
||||
|
||||
bool has_non_support_filament(const PrintConfig &config);
|
||||
bool is_last_extrusion_layer(coordf_t print_z, unsigned int extruder_id) const;
|
||||
|
||||
private:
|
||||
void initialize_layers(std::vector<coordf_t> &zs);
|
||||
@@ -413,6 +414,8 @@ private:
|
||||
// which keeps every legacy per-object code path bit-identical (loops over an empty map are
|
||||
// no-ops; downstream emission falls through to the per-object branch).
|
||||
std::map<unsigned int, std::map<LayerTools::MixedSubLayerGroup::VolumeKey, std::vector<size_t>>> m_gradient_volume_layers;
|
||||
// Map: extruder_id -> print_z (z coordinate) of the last layer it is used on
|
||||
std::map<unsigned int, coordf_t> m_last_layer_per_extruder;
|
||||
const PrintObject* m_print_object_ptr = nullptr;
|
||||
Print* m_print;
|
||||
bool m_sorted = false;
|
||||
|
||||
@@ -211,6 +211,35 @@ bool decode_colored_png(const ReadBuf &in_buf, ImageColorscale &out_img)
|
||||
return decode_colored_png(stream, out_img);
|
||||
}
|
||||
|
||||
bool is_decodable_8bit_png(const ReadBuf &pngbuf)
|
||||
{
|
||||
static const constexpr int PNG_SIG_BYTES = 8;
|
||||
|
||||
struct ReadBufStream stream{pngbuf};
|
||||
std::vector<png_byte> sig(PNG_SIG_BYTES, 0);
|
||||
stream.read(sig.data(), PNG_SIG_BYTES);
|
||||
if (!png_check_sig(sig.data(), PNG_SIG_BYTES))
|
||||
return false;
|
||||
|
||||
PNGDescr dsc;
|
||||
dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (!dsc.png)
|
||||
return false;
|
||||
dsc.info = png_create_info_struct(dsc.png);
|
||||
if (!dsc.info)
|
||||
return false;
|
||||
|
||||
if (!png_read_header_guarded(dsc.png, dsc.info, &stream, PNG_SIG_BYTES))
|
||||
return false;
|
||||
|
||||
const size_t color_type = png_get_color_type(dsc.png, dsc.info);
|
||||
const size_t bit_depth = png_get_bit_depth(dsc.png, dsc.info);
|
||||
const bool grey = color_type == PNG_COLOR_TYPE_GRAY;
|
||||
const bool colour = color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
return png_get_image_width(dsc.png, dsc.info) > 0 && png_get_image_height(dsc.png, dsc.info) > 0 &&
|
||||
bit_depth == 8 && (grey || colour);
|
||||
}
|
||||
|
||||
|
||||
// Down to earth function to store a packed RGB image to file. Mostly useful for debugging purposes.
|
||||
// Based on https://www.lemoda.net/c/write-png/
|
||||
|
||||
@@ -50,6 +50,9 @@ struct ReadBuf { const void *buf = nullptr; const size_t sz = 0; };
|
||||
|
||||
bool is_png(const ReadBuf &pngbuf);
|
||||
|
||||
// True if decode_png() or decode_colored_png() would accept these bytes, judged from the header alone.
|
||||
bool is_decodable_8bit_png(const ReadBuf &pngbuf);
|
||||
|
||||
struct ReadBufStream: public IStream {
|
||||
const ReadBuf &rbuf_ref;
|
||||
size_t pos = 0;
|
||||
|
||||
@@ -1489,7 +1489,7 @@ static std::vector<std::string> s_Preset_print_options{
|
||||
static std::vector<std::string> s_Preset_filament_options {/*"filament_colour", */ "default_filament_colour", "required_nozzle_HRC", "filament_diameter", "pellet_flow_coefficient", "volumetric_speed_coefficients", "filament_type",
|
||||
"filament_soluble", "filament_is_support", "filament_printable", "filament_extruder_compatibility",
|
||||
"filament_max_volumetric_speed", "filament_adaptive_volumetric_speed",
|
||||
"filament_flow_ratio", "filament_density", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower",
|
||||
"filament_flow_ratio", "filament_density", "filament_transmission_distance", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower",
|
||||
"filament_tower_interface_pre_extrusion_dist", "filament_tower_interface_pre_extrusion_length", "filament_tower_ironing_area", "filament_tower_interface_purge_volume",
|
||||
"filament_tower_interface_print_temp",
|
||||
"nozzle_temperature", "nozzle_temperature_initial_layer",
|
||||
|
||||
@@ -228,6 +228,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
"filament_diameter",
|
||||
"volumetric_speed_coefficients",
|
||||
"filament_density",
|
||||
"filament_transmission_distance",
|
||||
"filament_cost",
|
||||
"filament_notes",
|
||||
"outer_wall_acceleration",
|
||||
|
||||
@@ -3440,6 +3440,20 @@ void PrintConfigDef::init_fff_params()
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloats { 0. });
|
||||
|
||||
def = this->add("filament_transmission_distance", coFloats);
|
||||
def->label = L("Transmission distance");
|
||||
def->tooltip = L("How deep light gets into this filament, in millimetres, also called its "
|
||||
"transmission distance or TD. About 1 for an opaque filament, 4 to 7 for a usefully "
|
||||
"translucent one, 8 and above for a nearly clear one. Used only to predict the "
|
||||
"colours of a ChromaStack in the texture displacement tool; it has no effect on "
|
||||
"slicing. Measure it with a step test print, or take a published value for the "
|
||||
"filament.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 100;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloats { 4. });
|
||||
|
||||
def = this->add("filament_type", coStrings);
|
||||
def->label = L("Type");
|
||||
def->tooltip = L("Filament material type");
|
||||
|
||||
@@ -1709,6 +1709,9 @@ PRINT_CONFIG_CLASS_DEFINE(
|
||||
((ConfigOptionStrings, volumetric_speed_coefficients))
|
||||
((ConfigOptionInts, filament_adhesiveness_category))
|
||||
((ConfigOptionFloats, filament_density))
|
||||
// Transmission distance: how deep light gets into this filament. Not used by
|
||||
// slicing - it is the colour model of a ChromaStack stack (see ChromaStack.hpp).
|
||||
((ConfigOptionFloats, filament_transmission_distance))
|
||||
((ConfigOptionStrings, filament_type))
|
||||
((ConfigOptionBools, filament_soluble))
|
||||
((ConfigOptionStrings, filament_ids))
|
||||
|
||||
@@ -858,11 +858,10 @@ void PrintObject::contour_z()
|
||||
BOOST_LOG_TRIVIAL(debug) << "Contouring in parallel - start";
|
||||
|
||||
TriangleMesh mesh = this->m_model_object->raw_mesh();
|
||||
if (m_model_object->instances.size() != 1) {
|
||||
throw RuntimeError("ContourZ: unexpected number of instances");
|
||||
}
|
||||
|
||||
ModelInstance *inst = m_model_object->instances.front();
|
||||
// A PrintObject groups the instances that share their whole transformation but the XY shift: an
|
||||
// instance rotated, scaled or mirrored differently is a PrintObject of its own. So the first
|
||||
// instance stands for all of them.
|
||||
const ModelInstance *inst = m_instances.front().model_instance;
|
||||
Point center_offset = this->center_offset();
|
||||
Geometry::Transformation trans = inst->get_transformation();
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ bool height_texture_has_color(const TextureDisplacementLayer &layer)
|
||||
return decode_height_texture(layer).has_color();
|
||||
}
|
||||
|
||||
DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer)
|
||||
DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer, bool keep_decoded)
|
||||
{
|
||||
DecodedHeightTexture result;
|
||||
if (layer.empty())
|
||||
@@ -351,13 +351,15 @@ DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer
|
||||
}
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_decoded_texture_cache.mutex);
|
||||
// Opportunistically drop entries for image_data that no longer exists anywhere, so the
|
||||
// cache doesn't grow without bound across many add/remove-texture cycles in a long session.
|
||||
auto &entries = g_decoded_texture_cache.entries;
|
||||
for (auto it = entries.begin(); it != entries.end();)
|
||||
it = it->second.first.expired() ? entries.erase(it) : std::next(it);
|
||||
entries[key] = { std::weak_ptr<const std::vector<unsigned char>>(layer.image_data), result };
|
||||
if (keep_decoded) {
|
||||
std::lock_guard<std::mutex> lock(g_decoded_texture_cache.mutex);
|
||||
// Opportunistically drop entries for image_data that no longer exists anywhere, so the
|
||||
// cache doesn't grow without bound across many add/remove-texture cycles in a long session.
|
||||
auto &entries = g_decoded_texture_cache.entries;
|
||||
for (auto it = entries.begin(); it != entries.end();)
|
||||
it = it->second.first.expired() ? entries.erase(it) : std::next(it);
|
||||
entries[key] = { std::weak_ptr<const std::vector<unsigned char>>(layer.image_data), result };
|
||||
}
|
||||
}
|
||||
|
||||
// The image-adjustment panel's live edits, on top of the decode and before the smoothing. Before,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "ChromaStack.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "TriangleSelector.hpp"
|
||||
|
||||
@@ -353,16 +354,33 @@ struct TextureDisplacementLayer
|
||||
// for the boundary to be drawn on.
|
||||
bool color_enabled = false;
|
||||
|
||||
// ChromaStack (see ChromaStack.hpp): when set, `image_data` is not a texture the user picked but the
|
||||
// height map chroma_solve() computed, so that the whole displacement pipeline - preview, UV
|
||||
// editor, bake - goes on reading one grayscale height map and needs to know nothing about colour
|
||||
// stacks. The picture it was solved from is kept here instead, because the solve has to be redone
|
||||
// whenever the stack, the print's layer height or the picture itself changes.
|
||||
//
|
||||
// A ChromaStack layer gets its colour from filament swaps at fixed Z, which is what `chroma`
|
||||
// records and what the object's height ranges carry out. That is a different mechanism from
|
||||
// `color_enabled` (per-triangle filament assignment), and the two are mutually exclusive: the
|
||||
// panel turns colour off on a ChromaStack layer, since painting the relief in one filament per
|
||||
// triangle would hide the very stack the heights were solved against.
|
||||
bool chroma_enabled = false;
|
||||
ChromaStack chroma;
|
||||
std::shared_ptr<std::vector<unsigned char>> chroma_source;
|
||||
|
||||
bool empty() const { return !image_data || image_data->empty(); }
|
||||
|
||||
template<class Archive> void save(Archive &ar) const
|
||||
{
|
||||
std::string blob = image_data ? std::string(image_data->begin(), image_data->end()) : std::string();
|
||||
std::string source = chroma_source ? std::string(chroma_source->begin(), chroma_source->end()) : std::string();
|
||||
ar(slot, name, path, path_in_3mf, blob, depth_mm, tiling_scale, rotation_deg, offset, invert, tile_enabled,
|
||||
static_cast<int>(tile_method), static_cast<int>(projection_method), lscm_seam_angle_deg, islands,
|
||||
static_cast<int>(blend_mode), midlevel, island_padding_mm, lscm_seam_edges, view_project_right,
|
||||
view_project_up, smoothing, edge_smoothing, edge_smoothing_amount, auto_connect_islands, island_groups,
|
||||
lscm_uv_overrides, view_project_projective, view_project_matrix, color_enabled);
|
||||
lscm_uv_overrides, view_project_projective, view_project_matrix, color_enabled, chroma_enabled, chroma,
|
||||
source);
|
||||
}
|
||||
template<class Archive> void load(Archive &ar)
|
||||
{
|
||||
@@ -370,12 +388,14 @@ struct TextureDisplacementLayer
|
||||
int tile_method_int = 0;
|
||||
int projection_method_int = 0;
|
||||
int blend_mode_int = 0;
|
||||
std::string source;
|
||||
ar(slot, name, path, path_in_3mf, blob, depth_mm, tiling_scale, rotation_deg, offset, invert, tile_enabled,
|
||||
tile_method_int, projection_method_int, lscm_seam_angle_deg, islands, blend_mode_int, midlevel,
|
||||
island_padding_mm, lscm_seam_edges, view_project_right, view_project_up, smoothing, edge_smoothing,
|
||||
edge_smoothing_amount, auto_connect_islands, island_groups, lscm_uv_overrides, view_project_projective,
|
||||
view_project_matrix, color_enabled);
|
||||
view_project_matrix, color_enabled, chroma_enabled, chroma, source);
|
||||
image_data = blob.empty() ? nullptr : std::make_shared<std::vector<unsigned char>>(blob.begin(), blob.end());
|
||||
chroma_source = source.empty() ? nullptr : std::make_shared<std::vector<unsigned char>>(source.begin(), source.end());
|
||||
tile_method = static_cast<TextureTileMethod>(tile_method_int);
|
||||
projection_method = static_cast<TextureProjectionMethod>(projection_method_int);
|
||||
blend_mode = static_cast<TextureBlendMode>(blend_mode_int);
|
||||
@@ -599,8 +619,9 @@ DecodedHeightTexture downscale_height_texture(const DecodedHeightTexture &src, i
|
||||
// colour PNGs are accepted; for a colour source the height is its luminance, using the same
|
||||
// coefficients wxImage::ConvertToGreyscale() uses, so a texture imported as colour displaces exactly
|
||||
// as it did when the importer flattened it to grey on the way in. Returns an empty
|
||||
// DecodedHeightTexture if image_data is empty or is not a PNG at all.
|
||||
DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer);
|
||||
// DecodedHeightTexture if image_data is empty or is not a PNG at all. keep_decoded = false leaves the
|
||||
// result out of the decode cache, for one-off reads such as a picker thumbnail that must not hold full-size pixels.
|
||||
DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer, bool keep_decoded = true);
|
||||
|
||||
// decode_height_texture(layer).has_color(), answered from the decode cache rather than from a copy of the
|
||||
// texture - cheap enough to ask every frame. Smoothing does not change it, so the raw decode is what is read.
|
||||
|
||||
@@ -842,6 +842,13 @@ void load_string_file(const boost::filesystem::path& p, std::string& str);
|
||||
|
||||
bool check_layer_id_pattern(const std::string& pattern, int layer_id);
|
||||
|
||||
// On Windows, return an equivalent path carrying the extended-length ("\\?\" / "\\?\UNC\")
|
||||
// prefix so the Win32 APIs accept it past MAX_PATH even when LongPathsEnabled is off. The path
|
||||
// is made absolute and lexically normalized first, because the prefix disables normalisation.
|
||||
// A path that is already extended (or a device path) is returned unchanged; on other platforms,
|
||||
// or when an absolute path cannot be formed, the input is returned unchanged.
|
||||
boost::filesystem::path extended_path(const boost::filesystem::path& path);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#if WIN32
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW64__)
|
||||
#include "boost/nowide/cstdio.hpp"
|
||||
#include "boost/nowide/convert.hpp"
|
||||
#endif
|
||||
|
||||
#include "I18N.hpp"
|
||||
@@ -62,7 +63,11 @@ bool open_zip(mz_zip_archive *zip, const char *fname, bool isread)
|
||||
|
||||
FILE *f = nullptr;
|
||||
#if defined(_MSC_VER) || defined(__MINGW64__)
|
||||
f = boost::nowide::fopen(fname, mode);
|
||||
// fname is UTF-8 (the nowide contract). Route it through the shared extended-path helper so an
|
||||
// archive stored past MAX_PATH opens even with LongPathsEnabled off.
|
||||
const std::wstring wide_path = boost::nowide::widen(fname);
|
||||
const std::string extended = boost::nowide::narrow(Slic3r::extended_path(boost::filesystem::path(wide_path)).wstring());
|
||||
f = boost::nowide::fopen(extended.c_str(), mode);
|
||||
#elif defined(__GNUC__) && defined(_LARGEFILE64_SOURCE)
|
||||
f = fopen64(fname, mode);
|
||||
#else
|
||||
@@ -101,6 +106,14 @@ bool close_zip(mz_zip_archive *zip, bool isread)
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
// mz_file_write_func writing an extracted entry into an already-open FILE*.
|
||||
size_t mz_write_to_cfile(void *opaque, mz_uint64 /*file_ofs*/, const void *buffer, size_t size)
|
||||
{
|
||||
return std::fwrite(buffer, 1, size, static_cast<FILE *>(opaque));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool open_zip_reader(mz_zip_archive *zip, const std::string &fname)
|
||||
@@ -116,6 +129,36 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname)
|
||||
bool close_zip_reader(mz_zip_archive *zip) { return close_zip(zip, true); }
|
||||
bool close_zip_writer(mz_zip_archive *zip) { return close_zip(zip, false); }
|
||||
|
||||
bool extract_zip_entry_to_file(mz_zip_archive *zip, mz_uint file_index, const boost::filesystem::path &dest_path)
|
||||
{
|
||||
if (!zip)
|
||||
return false;
|
||||
#ifdef WIN32
|
||||
// Open the destination ourselves with a Windows extended-length path: miniz's narrow
|
||||
// extract_to_file cannot be relied on past MAX_PATH with LongPathsEnabled off, and its wide
|
||||
// variant takes a plain (unprefixed) path and a text-mode stream. This keeps the write binary.
|
||||
FILE *file = ::_wfopen(Slic3r::extended_path(dest_path).wstring().c_str(), L"wb");
|
||||
if (!file) {
|
||||
zip->m_last_error = MZ_ZIP_FILE_OPEN_FAILED;
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool written = mz_zip_reader_extract_to_callback(zip, file_index, mz_write_to_cfile, file, 0) != 0;
|
||||
const bool closed = std::fclose(file) == 0;
|
||||
if (!written || !closed) {
|
||||
if (written && !closed)
|
||||
zip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED;
|
||||
// Drop the partial entry rather than leave a truncated file behind.
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove(Slic3r::extended_path(dest_path), ec);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return mz_zip_reader_extract_to_file(zip, file_index, dest_path.string().c_str(), 0) != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat)
|
||||
{
|
||||
if (stat.m_is_utf8)
|
||||
@@ -156,10 +199,13 @@ bool extract_archive_confined(const std::string &zip_path_utf8, const std::strin
|
||||
}
|
||||
const std::string dest_file = dest_dir + "/" + stat.m_filename;
|
||||
try {
|
||||
// Use the extended-length form for every filesystem call so entries under a destination
|
||||
// past MAX_PATH are created even with LongPathsEnabled off.
|
||||
const boost::filesystem::path dest_path(dest_file);
|
||||
const boost::filesystem::path io_dest = extended_path(dest_path);
|
||||
if (stat.m_is_directory) {
|
||||
const boost::filesystem::path dest_path(dest_file);
|
||||
if (!boost::filesystem::exists(dest_path))
|
||||
boost::filesystem::create_directories(dest_path);
|
||||
if (!boost::filesystem::exists(io_dest))
|
||||
boost::filesystem::create_directories(io_dest);
|
||||
continue;
|
||||
}
|
||||
if (stat.m_uncomp_size == 0) {
|
||||
@@ -167,10 +213,9 @@ bool extract_archive_confined(const std::string &zip_path_utf8, const std::strin
|
||||
continue;
|
||||
}
|
||||
// Replace a symlink at the destination rather than writing through it.
|
||||
const boost::filesystem::path dest_path(dest_file);
|
||||
if (boost::filesystem::is_symlink(boost::filesystem::symlink_status(dest_path)))
|
||||
boost::filesystem::remove(dest_path);
|
||||
if (!mz_zip_reader_extract_to_file(&archive, stat.m_file_index, dest_file.c_str(), 0)) {
|
||||
if (boost::filesystem::is_symlink(boost::filesystem::symlink_status(io_dest)))
|
||||
boost::filesystem::remove(io_dest);
|
||||
if (!extract_zip_entry_to_file(&archive, stat.m_file_index, dest_path)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Unzip: extract file " << stat.m_filename << " to dest " << dest_file << " failed";
|
||||
close_zip_reader(&archive);
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef MINIZ_EXTENSION_HPP
|
||||
#define MINIZ_EXTENSION_HPP
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <string>
|
||||
#include <miniz.h>
|
||||
|
||||
@@ -13,6 +14,10 @@ bool close_zip_writer(mz_zip_archive *zip);
|
||||
std::string decode_archive_entry_path(mz_zip_archive *zip, const mz_zip_archive_file_stat &stat);
|
||||
// Extracts every entry of the archive under dest_dir. Nothing is written if any entry would resolve outside dest_dir.
|
||||
bool extract_archive_confined(const std::string &zip_path_utf8, const std::string &dest_dir);
|
||||
// Extracts one entry into dest_path, binary-safe and overwriting. On Windows the destination is
|
||||
// opened through an extended-length path so it works past MAX_PATH even with LongPathsEnabled off;
|
||||
// a partial file is removed if the write fails. False for a directory/unsupported entry or on error.
|
||||
bool extract_zip_entry_to_file(mz_zip_archive *zip, mz_uint file_index, const boost::filesystem::path &dest_path);
|
||||
|
||||
class MZ_Archive {
|
||||
public:
|
||||
|
||||
@@ -2254,5 +2254,45 @@ bool check_layer_id_pattern(const std::string& pattern, int layer_id){
|
||||
return mod >= 0 && mod < count;
|
||||
}
|
||||
|
||||
boost::filesystem::path extended_path(const boost::filesystem::path& path) {
|
||||
#ifdef WIN32
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
// Already extended, or an explicit device path (which is not subject to MAX_PATH and must not
|
||||
// be absolutized or normalized): leave it alone.
|
||||
{
|
||||
fs::path prefix_candidate = path;
|
||||
const std::wstring native = prefix_candidate.make_preferred().wstring();
|
||||
if (native.rfind(L"\\\\?\\", 0) == 0 || native.rfind(L"\\\\.\\", 0) == 0)
|
||||
return fs::path(native);
|
||||
}
|
||||
|
||||
fs::path absolute = path;
|
||||
if (!absolute.is_absolute()) {
|
||||
boost::system::error_code ec;
|
||||
absolute = fs::absolute(absolute, ec);
|
||||
if (ec)
|
||||
return path; // can't form a valid prefixed path from a relative one
|
||||
}
|
||||
|
||||
// A trailing separator is redundant to the Win32 APIs, but lexically_normal() would rewrite it
|
||||
// as a "." element, which the "\\?\" prefix (normalisation off) takes literally. Drop it first.
|
||||
while (absolute.has_relative_path() && absolute.filename() == ".")
|
||||
absolute = absolute.parent_path();
|
||||
|
||||
// The "\\?\" prefix turns off Win32 normalisation, so do it here first.
|
||||
absolute = absolute.lexically_normal();
|
||||
|
||||
// Extended paths only recognize backslash separators.
|
||||
const std::wstring native = absolute.make_preferred().wstring();
|
||||
if (native.empty())
|
||||
return path;
|
||||
if (native.rfind(L"\\\\", 0) == 0)
|
||||
return fs::path(L"\\\\?\\UNC\\" + native.substr(2)); // \\server\share\... -> \\?\UNC\server\share\...
|
||||
return fs::path(L"\\\\?\\" + native); // C:\dir\... -> \\?\C:\dir\...
|
||||
#else
|
||||
return path;
|
||||
#endif
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
@@ -222,6 +222,7 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Gizmos/GLGizmoUtils.hpp
|
||||
GUI/GLCanvas3D.cpp
|
||||
GUI/GLCanvas3D.hpp
|
||||
GUI/SequentialPrintClearance.hpp
|
||||
GUI/GLModel.cpp
|
||||
GUI/GLModel.hpp
|
||||
GUI/GLSelectionRectangle.cpp
|
||||
@@ -689,6 +690,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
plugin/host/PluginHostUi.hpp
|
||||
plugin/host/PluginPages.cpp
|
||||
plugin/host/PluginPages.hpp
|
||||
plugin/host/PluginResourceTextures.cpp
|
||||
plugin/host/PluginResourceTextures.hpp
|
||||
plugin/CloudPluginService.cpp
|
||||
plugin/CloudPluginService.hpp
|
||||
plugin/PluginFsUtils.cpp
|
||||
@@ -712,6 +715,9 @@ set(SLIC3R_GUI_SOURCES
|
||||
plugin/pluginTypes/pages/PagesPluginCapability.hpp
|
||||
plugin/pluginTypes/pages/PagesPluginCapability.cpp
|
||||
plugin/pluginTypes/pages/PagesPluginCapabilityTrampoline.hpp
|
||||
plugin/pluginTypes/resourceTexture/ResourceTexturePluginCapability.hpp
|
||||
plugin/pluginTypes/resourceTexture/ResourceTexturePluginCapability.cpp
|
||||
plugin/pluginTypes/resourceTexture/ResourceTexturePluginCapabilityTrampoline.hpp
|
||||
plugin/pluginTypes/script/ScriptPluginCapability.hpp
|
||||
plugin/pluginTypes/script/ScriptPluginCapability.cpp
|
||||
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp
|
||||
|
||||
@@ -85,6 +85,96 @@ static std::string float_to_string_with_precision(float value, int precision = 3
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
static std::string colour_to_ams_string(const wxColour& color)
|
||||
{
|
||||
char col_buf[10];
|
||||
sprintf(col_buf, "%02X%02X%02X%02X", (int)color.Red(), (int)color.Green(), (int)color.Blue(), (int)color.Alpha());
|
||||
return col_buf;
|
||||
}
|
||||
|
||||
static wxColour mix_colour(const wxColour& left, const wxColour& right, double ratio)
|
||||
{
|
||||
ratio = std::max(0.0, std::min(1.0, ratio));
|
||||
auto mix_channel = [ratio](unsigned char l, unsigned char r) {
|
||||
return static_cast<unsigned char>(std::round(l + (r - l) * ratio));
|
||||
};
|
||||
return wxColour(mix_channel(left.Red(), right.Red()),
|
||||
mix_channel(left.Green(), right.Green()),
|
||||
mix_channel(left.Blue(), right.Blue()),
|
||||
mix_channel(left.Alpha(), right.Alpha()));
|
||||
}
|
||||
|
||||
static std::vector<ColorPickerPopup::ColorItem> collect_ams_color_items(DevFilaSystem* fila_system)
|
||||
{
|
||||
std::vector<ColorPickerPopup::ColorItem> items;
|
||||
if (!fila_system) return items;
|
||||
|
||||
for (const auto& ams_pair : fila_system->GetAmsList()) {
|
||||
DevAms* ams = ams_pair.second;
|
||||
if (!ams) continue;
|
||||
|
||||
for (const auto& tray_pair : ams->GetTrays()) {
|
||||
DevAmsTray* tray = tray_pair.second;
|
||||
if (!tray || !tray->is_tray_info_ready()) continue;
|
||||
|
||||
ColorPickerPopup::ColorItem item;
|
||||
for (const std::string& col : tray->cols) {
|
||||
item.colors.emplace_back(DevAmsTray::decode_color(col));
|
||||
}
|
||||
if (item.colors.empty() && !tray->color.empty()) {
|
||||
item.colors.emplace_back(DevAmsTray::decode_color(tray->color));
|
||||
}
|
||||
if (item.colors.empty()) continue;
|
||||
|
||||
item.ctype = item.colors.size() > 1 ? tray->ctype : 2;
|
||||
items.emplace_back(std::move(item));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
static std::vector<ColorPickerPopup::ColorItem> collect_preset_color_items(const std::string& filament_id)
|
||||
{
|
||||
std::vector<ColorPickerPopup::ColorItem> items;
|
||||
if (filament_id.empty()) return items;
|
||||
|
||||
auto* clr_query = GUI::wxGetApp().get_filament_color_code_query();
|
||||
if (!clr_query) return items;
|
||||
|
||||
auto* agent = wxGetApp().getAgent();
|
||||
const std::string catalog_id = agent ? agent->from_orca_filament_id(filament_id) : filament_id;
|
||||
FilamentColorCodes* color_codes = clr_query->GetFilaInfoMap(wxString::FromUTF8(catalog_id));
|
||||
if (!color_codes || !color_codes->GetFilamentColor2CodeMap()) return items;
|
||||
|
||||
std::set<std::string> seen;
|
||||
for (const auto& color_pair : *color_codes->GetFilamentColor2CodeMap()) {
|
||||
const FilamentColor& fila_color = color_pair.first;
|
||||
FilamentColorCode* color_code = color_pair.second;
|
||||
if (!color_code || fila_color.GetColors().empty()) continue;
|
||||
|
||||
ColorPickerPopup::ColorItem item;
|
||||
item.ctype = fila_color.ColorCount() < 2 ? 2 :
|
||||
(fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR ? 0 : 1);
|
||||
item.name = color_code->GetFilaColorName();
|
||||
|
||||
std::string key = std::to_string(item.ctype);
|
||||
for (const wxColour& color : fila_color.GetColors()) {
|
||||
item.colors.emplace_back(color);
|
||||
char rgba[9];
|
||||
std::snprintf(rgba, sizeof(rgba), "%02X%02X%02X%02X",
|
||||
static_cast<unsigned>(color.Red()), static_cast<unsigned>(color.Green()),
|
||||
static_cast<unsigned>(color.Blue()), static_cast<unsigned>(color.Alpha()));
|
||||
key += "|" + std::string(rgba);
|
||||
}
|
||||
|
||||
if (item.colors.empty() || !seen.insert(key).second) continue;
|
||||
items.emplace_back(std::move(item));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
AMSMaterialsSetting::AMSMaterialsSetting(wxWindow *parent, wxWindowID id)
|
||||
: DPIDialog(parent, id, _L("AMS Materials Setting"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
|
||||
, m_color_picker_popup(ColorPickerPopup(this))
|
||||
@@ -740,8 +830,19 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
||||
nozzle_temp_min.ToLong(&nozzle_temp_min_int);
|
||||
nozzle_temp_max.ToLong(&nozzle_temp_max_int);
|
||||
wxColour color = m_clr_picker->m_colour;
|
||||
char col_buf[10];
|
||||
sprintf(col_buf, "%02X%02X%02X%02X", (int)color.Red(), (int)color.Green(), (int)color.Blue(), (int)color.Alpha());
|
||||
std::string tray_color = colour_to_ams_string(color);
|
||||
std::vector<std::string> tray_colors;
|
||||
int tray_ctype = 2;
|
||||
// Only firmware advertising fun2[23] receives the manual multi-color fields.
|
||||
if (obj->is_support_filament_manual_multi_color) {
|
||||
for (const wxColour& selected_color : m_clr_picker->m_cols) {
|
||||
tray_colors.emplace_back(colour_to_ams_string(selected_color));
|
||||
}
|
||||
if (tray_colors.empty()) {
|
||||
tray_colors.emplace_back(tray_color);
|
||||
}
|
||||
tray_ctype = tray_colors.size() > 1 ? m_clr_picker->ctype : 2;
|
||||
}
|
||||
|
||||
if (ams_filament_id.empty() || nozzle_temp_min.empty() || nozzle_temp_max.empty() || m_filament_type.empty()) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Invalid Setting id";
|
||||
@@ -760,13 +861,33 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
|
||||
<< ", tray_info_idx (filament_id) = " << ams_filament_id
|
||||
<< ", setting_id = " << ams_setting_id
|
||||
<< ", tray_type = " << m_filament_type
|
||||
<< ", tray_color = " << col_buf
|
||||
<< ", tray_color = " << tray_color
|
||||
<< ", nozzle_temp_min = " << nozzle_temp_min_int
|
||||
<< ", nozzle_temp_max = " << nozzle_temp_max_int;
|
||||
|
||||
// set filament
|
||||
if (m_is_third) {
|
||||
obj->command_ams_filament_settings(ams_id, slot_id, ams_filament_id, ams_setting_id, std::string(col_buf), m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int);
|
||||
obj->command_ams_filament_settings(ams_id, slot_id, ams_filament_id, ams_setting_id, tray_color, m_filament_type, nozzle_temp_min_int, nozzle_temp_max_int,
|
||||
tray_colors, tray_ctype);
|
||||
|
||||
// Refresh the edited tray immediately while its MQTT acknowledgement is pending.
|
||||
DevAmsTray* tray = nullptr;
|
||||
if (is_virtual_tray()) {
|
||||
for (auto& virtual_tray : obj->vt_slot) {
|
||||
if (virtual_tray.id == std::to_string(ams_id)) {
|
||||
tray = &virtual_tray;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tray = obj->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
|
||||
}
|
||||
if (tray) {
|
||||
tray->color = tray_color;
|
||||
tray->cols = tray_colors;
|
||||
tray->ctype = tray_ctype;
|
||||
tray->set_hold_count();
|
||||
}
|
||||
}
|
||||
|
||||
//reset param
|
||||
@@ -923,8 +1044,15 @@ void AMSMaterialsSetting::set_ctype(int ctype)
|
||||
|
||||
void AMSMaterialsSetting::on_picker_color(wxCommandEvent& event)
|
||||
{
|
||||
unsigned int color_num = event.GetInt();
|
||||
set_color(wxColour(color_num>>24&0xFF, color_num>>16&0xFF, color_num>>8&0xFF, color_num&0xFF));
|
||||
std::vector<wxColour> colors = m_color_picker_popup.get_selected_colours();
|
||||
if (colors.empty()) {
|
||||
unsigned int color_num = event.GetInt();
|
||||
colors.emplace_back(color_num >> 24 & 0xFF, color_num >> 16 & 0xFF, color_num >> 8 & 0xFF, color_num & 0xFF);
|
||||
}
|
||||
|
||||
set_ctype(m_color_picker_popup.get_selected_ctype());
|
||||
set_color(colors.front());
|
||||
set_colors(colors);
|
||||
}
|
||||
|
||||
void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
|
||||
@@ -939,14 +1067,12 @@ void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<wxColour> ams_colors;
|
||||
obj->GetFilaSystem()->CollectAmsColors(ams_colors);
|
||||
|
||||
m_color_picker_popup.set_ams_colours(collect_ams_color_items(obj->GetFilaSystem().get()));
|
||||
m_color_picker_popup.set_preset_colours(collect_preset_color_items(ams_filament_id));
|
||||
m_color_picker_popup.set_def_colour(m_clr_picker->m_colour, m_clr_picker->m_cols, m_clr_picker->ctype);
|
||||
wxPoint img_pos = m_clr_picker->ClientToScreen(wxPoint(0, 0));
|
||||
wxPoint popup_pos(img_pos.x - m_color_picker_popup.GetSize().x - FromDIP(95), img_pos.y - FromDIP(65));
|
||||
m_color_picker_popup.Position(popup_pos, wxSize(0, 0));
|
||||
m_color_picker_popup.set_ams_colours(ams_colors);
|
||||
m_color_picker_popup.set_def_colour(m_clr_picker->m_colour);
|
||||
m_color_picker_popup.Popup();
|
||||
}
|
||||
|
||||
@@ -1735,7 +1861,46 @@ void ColorPicker::doRender(wxDC& dc)
|
||||
auto radius = m_show_full ? size.x / 2 - FromDIP(1) : size.x / 2;
|
||||
if (m_selected) radius -= FromDIP(1);
|
||||
|
||||
if (alpha == 0) {
|
||||
if (m_show_full && m_cols.size() > 1) {
|
||||
if (ctype == 0) {
|
||||
const double center_x = size.x / 2.0;
|
||||
const double center_y = size.y / 2.0;
|
||||
const double draw_radius = radius;
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
for (int x = 0; x < size.x; ++x) {
|
||||
const double dx = x + 0.5 - center_x;
|
||||
if (std::abs(dx) > draw_radius) continue;
|
||||
|
||||
const double half_height = std::sqrt(std::max(0.0, draw_radius * draw_radius - dx * dx));
|
||||
const int top = std::max(0, static_cast<int>(std::ceil(center_y - half_height)));
|
||||
const int bottom = std::min(size.y - 1, static_cast<int>(std::floor(center_y + half_height)));
|
||||
const double pos = size.x > 1 ? static_cast<double>(x) / (size.x - 1) : 0.0;
|
||||
const double scaled = pos * (m_cols.size() - 1);
|
||||
const size_t idx = std::min(static_cast<size_t>(scaled), m_cols.size() - 2);
|
||||
const double ratio = scaled - idx;
|
||||
|
||||
if (top <= bottom) {
|
||||
dc.SetBrush(wxBrush(mix_colour(m_cols[idx], m_cols[idx + 1], ratio)));
|
||||
dc.DrawRectangle(x, top, 1, bottom - top + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
float ev_angle = 360.0 / m_cols.size();
|
||||
const float overlap = 2.0f;
|
||||
wxPoint center(size.x / 2, size.y / 2);
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(wxBrush(m_cols.front()));
|
||||
dc.DrawCircle(center.x, center.y, radius);
|
||||
for (int i = 0; i < m_cols.size(); i++) {
|
||||
dc.SetBrush(m_cols[i]);
|
||||
float startAngle = 270.0f + i * ev_angle;
|
||||
float endAngle = startAngle + ev_angle;
|
||||
dc.DrawEllipticArc(center.x - radius, center.y - radius, 2 * radius, 2 * radius, startAngle - overlap, endAngle + overlap);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (alpha == 0) {
|
||||
wxSize bmp_size = m_bitmap_transparent_def.GetBmpSize();
|
||||
int center_x = (size.x - bmp_size.x) / 2;
|
||||
int center_y = (size.y - bmp_size.y) / 2;
|
||||
@@ -1777,52 +1942,6 @@ void ColorPicker::doRender(wxDC& dc)
|
||||
dc.SetPen(wxPen(wxColour("#6B6B6B")));
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawCircle(size.x / 2, size.y / 2, radius);
|
||||
|
||||
if (m_cols.size() > 1) {
|
||||
if (ctype == 0) {
|
||||
int left = FromDIP(0);
|
||||
float total_width = size.x;
|
||||
int gwidth = std::round(total_width / (m_cols.size() - 1));
|
||||
|
||||
for (int i = 0; i < m_cols.size() - 1; i++) {
|
||||
|
||||
if ((left + gwidth) > (size.x)) {
|
||||
gwidth = size.x - left;
|
||||
}
|
||||
|
||||
auto rect = wxRect(left, 0, gwidth, size.y);
|
||||
dc.GradientFillLinear(rect, m_cols[i], m_cols[i + 1], wxEAST);
|
||||
left += gwidth;
|
||||
}
|
||||
if (wxGetApp().dark_mode()) {
|
||||
dc.DrawBitmap(m_bitmap_border_dark, wxPoint(0, 0));
|
||||
}
|
||||
else {
|
||||
dc.DrawBitmap(m_bitmap_border, wxPoint(0, 0));
|
||||
}
|
||||
}
|
||||
else {
|
||||
float ev_angle = 360.0 / m_cols.size();
|
||||
float startAngle = 270.0;
|
||||
float endAngle = 270.0;
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
for (int i = 0; i < m_cols.size(); i++) {
|
||||
dc.SetBrush(m_cols[i]);
|
||||
endAngle += ev_angle;
|
||||
endAngle = endAngle > 360.0 ? endAngle - 360.0 : endAngle;
|
||||
wxPoint center(size.x / 2, size.y / 2);
|
||||
dc.DrawEllipticArc(center.x - radius, center.y - radius, 2 * radius, 2 * radius, startAngle, endAngle);
|
||||
startAngle += ev_angle;
|
||||
startAngle = startAngle > 360.0 ? startAngle - 360.0 : startAngle;
|
||||
}
|
||||
if (wxGetApp().dark_mode()) {
|
||||
dc.DrawBitmap(m_bitmap_border_dark, wxPoint(0, 0));
|
||||
}
|
||||
else {
|
||||
dc.DrawBitmap(m_bitmap_border, wxPoint(0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_is_empty) {
|
||||
@@ -1891,19 +2010,21 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
|
||||
m_ams_fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
//other
|
||||
wxFlexGridSizer* fg_sizer;
|
||||
fg_sizer = new wxFlexGridSizer(0, 8, 0, 0);
|
||||
fg_sizer->SetFlexibleDirection(wxBOTH);
|
||||
fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
m_other_fg_sizer = new wxFlexGridSizer(0, 8, 0, 0);
|
||||
m_other_fg_sizer->SetFlexibleDirection(wxBOTH);
|
||||
m_other_fg_sizer->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
||||
|
||||
|
||||
for (wxColour col : m_def_colors) {
|
||||
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
cp->set_color(col);
|
||||
cp->set_colors({col});
|
||||
cp->ctype = 2;
|
||||
cp->set_selected(false);
|
||||
cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238)));
|
||||
m_color_pickers.push_back(cp);
|
||||
fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
m_default_color_pickers.push_back(cp);
|
||||
m_other_fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
|
||||
set_def_colour(cp->m_colour);
|
||||
|
||||
@@ -1970,7 +2091,7 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
|
||||
m_sizer_box->Add(m_sizer_ams, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_ams_fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_sizer_other, 1, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_other_fg_sizer, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_sizer_custom, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(10));
|
||||
m_sizer_box->Add(m_custom_cp, 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(16));
|
||||
m_sizer_box->Add(0, 0, 0, wxTOP, FromDIP(10));
|
||||
@@ -1981,7 +2102,7 @@ ColorPickerPopup::ColorPickerPopup(wxWindow* parent)
|
||||
m_def_color_box->Fit();
|
||||
|
||||
m_sizer_main->Add(m_def_color_box, 0, wxALL | wxEXPAND, 10);
|
||||
SetSizer(m_sizer_main);
|
||||
SetSizerAndFit(m_sizer_main);
|
||||
Layout();
|
||||
Fit();
|
||||
|
||||
@@ -2019,13 +2140,15 @@ void ColorPickerPopup::on_custom_clr_picker(wxMouseEvent& /*event*/)
|
||||
wxPostEvent(GetParent(), evt);
|
||||
}
|
||||
|
||||
void ColorPickerPopup::set_ams_colours(std::vector<wxColour> ams)
|
||||
void ColorPickerPopup::set_ams_colours(const std::vector<ColorItem>& ams)
|
||||
{
|
||||
m_ams_fg_sizer->Clear(false);
|
||||
if (m_ams_color_pickers.size() > 0) {
|
||||
for (ColorPicker* col_pick:m_ams_color_pickers) {
|
||||
|
||||
std::vector<ColorPicker*>::iterator iter = find(m_color_pickers.begin(), m_color_pickers.end(), col_pick);
|
||||
if (iter != m_color_pickers.end()) {
|
||||
col_pick->Hide();
|
||||
col_pick->Destroy();
|
||||
m_color_pickers.erase(iter);
|
||||
}
|
||||
@@ -2035,17 +2158,22 @@ void ColorPickerPopup::set_ams_colours(std::vector<wxColour> ams)
|
||||
}
|
||||
|
||||
|
||||
m_ams_colors = ams;
|
||||
for (wxColour col : m_ams_colors) {
|
||||
m_ams_color_items = ams;
|
||||
for (const ColorItem& item : m_ams_color_items) {
|
||||
if (item.colors.empty()) continue;
|
||||
|
||||
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
cp->set_color(col);
|
||||
cp->set_color(item.colors.front());
|
||||
cp->set_colors(item.colors);
|
||||
cp->ctype = item.colors.size() > 1 ? item.ctype : 2;
|
||||
cp->set_show_full(true);
|
||||
cp->set_selected(false);
|
||||
cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238)));
|
||||
m_color_pickers.push_back(cp);
|
||||
m_ams_color_pickers.push_back(cp);
|
||||
m_ams_fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
|
||||
set_def_colour(cp->m_colour);
|
||||
set_def_colour(cp->m_colour, cp->m_cols, cp->ctype);
|
||||
|
||||
wxCommandEvent evt(EVT_SELECTED_COLOR);
|
||||
unsigned long g_col = ((cp->m_colour.Red() & 0xff) << 24) + ((cp->m_colour.Green() & 0xff) << 16) + ((cp->m_colour.Blue() & 0xff) << 8) + (cp->m_colour.Alpha() & 0xff);
|
||||
@@ -2058,9 +2186,68 @@ void ColorPickerPopup::set_ams_colours(std::vector<wxColour> ams)
|
||||
Fit();
|
||||
}
|
||||
|
||||
void ColorPickerPopup::set_def_colour(wxColour col)
|
||||
void ColorPickerPopup::set_preset_colours(const std::vector<ColorItem>& preset_colors)
|
||||
{
|
||||
m_other_fg_sizer->Clear(false);
|
||||
if (!m_preset_color_pickers.empty()) {
|
||||
for (ColorPicker* col_pick : m_preset_color_pickers) {
|
||||
auto iter = std::find(m_color_pickers.begin(), m_color_pickers.end(), col_pick);
|
||||
if (iter != m_color_pickers.end()) {
|
||||
col_pick->Hide();
|
||||
col_pick->Destroy();
|
||||
m_color_pickers.erase(iter);
|
||||
}
|
||||
}
|
||||
m_preset_color_pickers.clear();
|
||||
}
|
||||
|
||||
for (const ColorItem& item : preset_colors) {
|
||||
if (item.colors.empty()) continue;
|
||||
|
||||
auto cp = new ColorPicker(m_def_color_box, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
cp->set_color(item.colors.front());
|
||||
cp->set_colors(item.colors);
|
||||
cp->ctype = item.colors.size() > 1 ? item.ctype : 2;
|
||||
cp->set_show_full(true);
|
||||
cp->set_selected(false);
|
||||
cp->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(238,238,238)));
|
||||
if (!item.name.empty()) {
|
||||
cp->SetToolTip(item.name);
|
||||
}
|
||||
|
||||
m_color_pickers.push_back(cp);
|
||||
m_preset_color_pickers.push_back(cp);
|
||||
cp->Bind(wxEVT_LEFT_DOWN, [this, cp](auto& e) {
|
||||
set_def_colour(cp->m_colour, cp->m_cols, cp->ctype);
|
||||
|
||||
wxCommandEvent evt(EVT_SELECTED_COLOR);
|
||||
unsigned long g_col = ((cp->m_colour.Red() & 0xff) << 24) + ((cp->m_colour.Green() & 0xff) << 16) + ((cp->m_colour.Blue() & 0xff) << 8) + (cp->m_colour.Alpha() & 0xff);
|
||||
evt.SetInt(g_col);
|
||||
wxPostEvent(GetParent(), evt);
|
||||
});
|
||||
}
|
||||
|
||||
const bool show_default_colours = m_preset_color_pickers.empty();
|
||||
for (ColorPicker* cp : m_default_color_pickers) {
|
||||
cp->Show(show_default_colours);
|
||||
}
|
||||
for (ColorPicker* cp : (show_default_colours ? m_default_color_pickers : m_preset_color_pickers)) {
|
||||
m_other_fg_sizer->Add(cp, 0, wxALL, FromDIP(3));
|
||||
}
|
||||
|
||||
m_other_fg_sizer->Layout();
|
||||
Layout();
|
||||
Fit();
|
||||
}
|
||||
|
||||
void ColorPickerPopup::set_def_colour(wxColour col, std::vector<wxColour> cols, int ctype)
|
||||
{
|
||||
m_def_col = col;
|
||||
if (cols.empty()) {
|
||||
cols.push_back(col);
|
||||
}
|
||||
m_def_cols = cols;
|
||||
m_def_ctype = m_def_cols.size() > 1 ? ctype : 2;
|
||||
|
||||
for (ColorPicker* cp : m_color_pickers) {
|
||||
if (cp->m_selected) {
|
||||
@@ -2069,7 +2256,8 @@ void ColorPickerPopup::set_def_colour(wxColour col)
|
||||
}
|
||||
|
||||
for (ColorPicker* cp : m_color_pickers) {
|
||||
if (cp->m_colour == m_def_col) {
|
||||
if (!cp->IsShown()) continue;
|
||||
if (cp->ctype == m_def_ctype && cp->m_cols == m_def_cols) {
|
||||
cp->set_selected(true);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -82,14 +82,22 @@ public:
|
||||
class ColorPickerPopup : public PopupWindow
|
||||
{
|
||||
public:
|
||||
struct ColorItem
|
||||
{
|
||||
std::vector<wxColour> colors;
|
||||
int ctype = 2;
|
||||
wxString name;
|
||||
};
|
||||
|
||||
ScalableBitmap m_ts_bitmap_custom;
|
||||
wxStaticBitmap* m_ts_stbitmap_custom;
|
||||
StaticBox* m_custom_cp;
|
||||
StaticBox* m_def_color_box;
|
||||
wxFlexGridSizer* m_ams_fg_sizer;
|
||||
wxFlexGridSizer* m_other_fg_sizer;
|
||||
wxColour m_def_col;
|
||||
std::vector<wxColour> m_def_colors;
|
||||
std::vector<wxColour> m_ams_colors;
|
||||
std::vector<ColorItem> m_ams_color_items;
|
||||
std::vector<ColorPicker*> m_color_pickers;
|
||||
std::vector<ColorPicker*> m_ams_color_pickers;
|
||||
|
||||
@@ -97,13 +105,20 @@ public:
|
||||
ColorPickerPopup(wxWindow* parent);
|
||||
~ColorPickerPopup() {};
|
||||
void on_custom_clr_picker(wxMouseEvent& event);
|
||||
void set_ams_colours(std::vector<wxColour> ams);
|
||||
void set_def_colour(wxColour col);
|
||||
void set_ams_colours(const std::vector<ColorItem>& ams);
|
||||
void set_preset_colours(const std::vector<ColorItem>& preset_colors);
|
||||
void set_def_colour(wxColour col, std::vector<wxColour> cols = {}, int ctype = 2);
|
||||
const std::vector<wxColour>& get_selected_colours() const { return m_def_cols; }
|
||||
int get_selected_ctype() const { return m_def_ctype; }
|
||||
void paintEvent(wxPaintEvent& evt);
|
||||
virtual void OnDismiss() wxOVERRIDE;
|
||||
virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE;
|
||||
|
||||
public:
|
||||
private:
|
||||
std::vector<ColorPicker*> m_default_color_pickers;
|
||||
std::vector<ColorPicker*> m_preset_color_pickers;
|
||||
std::vector<wxColour> m_def_cols;
|
||||
int m_def_ctype = 2;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -747,7 +747,9 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model& model, const Dyn
|
||||
|
||||
// Orca: prevent resetting under gcode viewer mode
|
||||
if (invalidated != PrintBase::APPLY_STATUS_UNCHANGED) {
|
||||
const auto plater = GUI::wxGetApp().mainframe->m_plater;
|
||||
// Use the app’s plater accessor instead of going through mainframe, otherwise anything during startup that reaches apply()
|
||||
// (loading presets, config or input files, e.g. from the CLI) therefore dereferences a null `mainframe` and crashes.
|
||||
const auto plater = GUI::wxGetApp().plater();
|
||||
if (plater && plater->only_gcode_mode()) {
|
||||
invalidated = PrintBase::APPLY_STATUS_UNCHANGED;
|
||||
}
|
||||
|
||||
@@ -1798,7 +1798,8 @@ int MachineObject::command_ams_calibrate(int ams_id)
|
||||
return command_with_dialog(m_agent->command_ams_calibrate(get_dev_id(), ams_id, MachineObject::m_sequence_id++, is_lan_mode_printer()));
|
||||
}
|
||||
|
||||
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max)
|
||||
int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type,
|
||||
int nozzle_temp_min, int nozzle_temp_max, const std::vector<std::string>& tray_colors, int tray_ctype)
|
||||
{
|
||||
int tag_tray_id = 0;
|
||||
int tag_ams_id = ams_id;
|
||||
@@ -1827,6 +1828,10 @@ int MachineObject::command_ams_filament_settings(int ams_id, int slot_id, std::s
|
||||
j["print"]["nozzle_temp_min"] = nozzle_temp_min;
|
||||
j["print"]["nozzle_temp_max"] = nozzle_temp_max;
|
||||
j["print"]["tray_type"] = tray_type;
|
||||
if (!tray_colors.empty()) {
|
||||
j["print"]["cols"] = tray_colors;
|
||||
j["print"]["ctype"] = tray_ctype;
|
||||
}
|
||||
|
||||
return this->publish_json(j);
|
||||
}
|
||||
@@ -5478,6 +5483,7 @@ void MachineObject::parse_new_info(json print)
|
||||
is_support_pa_mode = (get_flag_bits_no_border(fun2, 3) == 1);
|
||||
is_support_remote_dry = (get_flag_bits_no_border(fun2, 5) == 1);
|
||||
is_support_check_track_switch_match_slice_printer = get_flag_bits_no_border(fun2, 19) == 1;
|
||||
is_support_filament_manual_multi_color = (get_flag_bits_no_border(fun2, 23) == 1);
|
||||
|
||||
if (DevPrinterConfigUtil::support_print_check_firmware_for_tpu_left(printer_type)) {
|
||||
m_firmware_support_print_tpu_left = get_flag_bits_no_border(fun2, 7) == 1;
|
||||
|
||||
@@ -674,6 +674,7 @@ public:
|
||||
bool is_support_remote_dry = false;
|
||||
bool is_support_check_track_switch_match_slice_printer{false};
|
||||
bool is_support_pa_mode{false};
|
||||
bool is_support_filament_manual_multi_color{false};
|
||||
std::optional<bool> m_firmware_support_print_tpu_left;
|
||||
|
||||
bool installed_upgrade_kit{false};
|
||||
@@ -804,7 +805,7 @@ public:
|
||||
int command_ams_switch_filament(bool switch_filament);
|
||||
int command_ams_air_print_detect(bool air_print_detect);
|
||||
int command_ams_calibrate(int ams_id);
|
||||
int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max);
|
||||
int command_ams_filament_settings(int ams_id, int slot_id, std::string filament_id, std::string setting_id, std::string tray_color, std::string tray_type, int nozzle_temp_min, int nozzle_temp_max, const std::vector<std::string>& tray_colors = {}, int tray_ctype = 2);
|
||||
int command_ams_select_tray(std::string tray_id);
|
||||
int command_ams_refresh_rfid(int ams_id, int slot_id);
|
||||
int command_ams_control(std::string action);
|
||||
|
||||
@@ -96,7 +96,7 @@ void FilamentColorCodeQuery::LoadFromLocal()
|
||||
const auto& fila_color_strs = json_data_item["fila_color"].get<std::vector<wxString>>();
|
||||
for (const auto& color_str : fila_color_strs) {
|
||||
if (color_str.size() > 3) /* Skip the value like "#0"*/{
|
||||
fila_color.m_colors.emplace(wxColour(color_str));
|
||||
fila_color.AddColor(wxColour(color_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user