Merge branch 'main' into feature/texture_displacement

This commit is contained in:
SoftFever
2026-07-21 19:50:35 +08:00
631 changed files with 2326755 additions and 1243600 deletions
+70
View File
@@ -129,6 +129,23 @@ if (MINGW)
set_target_properties(OrcaSlicer PROPERTIES PREFIX "")
endif (MINGW)
# The GUI embeds the bundled shared libpython (libslic3r_gui -> pybind11::embed);
# the dep recipe stamps it with an @rpath id / plain SONAME, so the executable
# needs an rpath entry for each layout in the "Bundled Python/uv layout" map
# further down. Dead entries are skipped by the loader.
# OrcaSlicer_profile_validator links libslic3r only (no Python).
if (APPLE)
# A linker flag rather than the BUILD_RPATH property because the release
# flow uses the Xcode generator, which does not honor BUILD_RPATH.
target_link_options(OrcaSlicer PRIVATE "LINKER:-rpath,@executable_path/python/lib")
elseif (UNIX AND NOT WIN32)
set_target_properties(OrcaSlicer PROPERTIES
# Build tree + AppImage, which packages the build-tree binary.
BUILD_RPATH "$ORIGIN/python/lib;$ORIGIN/../lib/python/lib"
# Flatpak (cmake --install).
INSTALL_RPATH "$ORIGIN/../libpython/lib")
endif ()
if (NOT WIN32 AND NOT APPLE)
# Binary name on unix like systems (Linux, Unix)
set_target_properties(OrcaSlicer PROPERTIES OUTPUT_NAME "orca-slicer")
@@ -197,6 +214,26 @@ endif ()
set(output_dlls_Release "")
set(output_dlls_Debug "")
set(output_dlls_RelWithDebInfo "")
# ---- Bundled Python/uv layout (canonical map) ---------------------------
# The runtime and uv are staged three ways because packaging differs:
# install() rules - Windows installer; on FHS/Flatpak they stage
# only uv (Flatpak's runtime comes from the deps
# build installing to /app/libpython; plain FHS
# installs bundle no python runtime)
# POST_BUILD copies (here) - build-tree runs, and the macOS .app/AppImage
# flows, which package the build tree and
# never run install()
# packaging scripts - build_release_macos.sh (copies the .app),
# build_linux_image.sh.in (assembles $APPDIR)
# Final locations relative to the executable:
# Windows: <exe dir>/python <resources>/tools/uv
# macOS: Contents/MacOS/python Contents/MacOS/tools/uv
# AppImage: $APPDIR/lib/python $APPDIR/resources/tools/uv
# Flatpak: /app/libpython (dep prefix) /app/share/OrcaSlicer/tools/uv
# Runtime lookup: PythonInterpreter::initialize() / bundled_uv_path().
# libpython resolution: rpath on the OrcaSlicer target (above); the dep
# recipe gives the bundled interpreter a self-relative rpath.
# --------------------------------------------------------------------------
if (WIN32)
# This has to be a separate target due to the windows command line length limits
add_custom_target(COPY_DLLS ALL DEPENDS OrcaSlicer)
@@ -231,6 +268,12 @@ if (WIN32)
VERBATIM
)
endif ()
# copy libpython to the bin folder for Windows
add_custom_command(TARGET OrcaSlicer POST_BUILD
COMMAND ${CMAKE_COMMAND} -E rm -rf "$<TARGET_FILE_DIR:OrcaSlicer>/python"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_PREFIX_PATH}/libpython" "$<TARGET_FILE_DIR:OrcaSlicer>/python"
COMMENT "Copying Python runtime into the build tree"
VERBATIM)
else ()
@@ -248,25 +291,51 @@ else ()
if (XCODE)
# Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources")
set(BIN_DIR "${CMAKE_CURRENT_BINARY_DIR}")
set(BIN_CONF_DIR "Debug")
else ()
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/../resources")
set(BIN_DIR "$<TARGET_FILE_DIR:OrcaSlicer>")
endif ()
if (CMAKE_MACOSX_BUNDLE)
if (CMAKE_CONFIGURATION_TYPES)
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/OrcaSlicer.app/Contents/Resources")
set(BIN_DIR "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/OrcaSlicer.app/Contents/MacOS")
else()
set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.app/Contents/Resources")
set(BIN_DIR "${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.app/Contents/MacOS")
endif()
set(MACOSX_BUNDLE_ICON_FILE Icon.icns)
set(MACOSX_BUNDLE_BUNDLE_NAME "OrcaSlicer")
set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${SoftFever_VERSION})
set(MACOSX_BUNDLE_COPYRIGHT "Copyright(C) 2022-2024 Li Jiang All Rights Reserved")
if (XCODE)
# Xcode's CodeSign phase fails on the bundled Python runtime's dotted
# dirs (python3.12) under Contents/MacOS. Skip it for local dev builds
# and let the linker ad-hoc sign; the shipped bundle is signed by the
# Ninja/CI packaging path (build_release_macos.sh, build_orca.yml).
set_target_properties(OrcaSlicer PROPERTIES XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED NO)
endif()
endif()
add_custom_command(TARGET OrcaSlicer POST_BUILD
COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${BIN_RESOURCES_DIR}"
COMMENT "Symlinking the resources directory into the build tree"
VERBATIM)
add_custom_command(TARGET OrcaSlicer POST_BUILD
COMMAND ${CMAKE_COMMAND} -E rm -rf "${BIN_DIR}/python"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_PREFIX_PATH}/libpython" "${BIN_DIR}/python"
COMMENT "Copying Python runtime into the build tree"
VERBATIM)
# Stage uv next to the binary for the build-tree/.app flows, which never
# run install() -- see the layout map above; lookup is bundled_uv_path().
if(ORCA_BUNDLED_UV_EXECUTABLE AND EXISTS "${ORCA_BUNDLED_UV_EXECUTABLE}")
add_custom_command(TARGET OrcaSlicer POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${BIN_DIR}/tools/uv"
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ORCA_BUNDLED_UV_EXECUTABLE}" "${BIN_DIR}/tools/uv/${ORCA_BUNDLED_UV_FILENAME}"
COMMAND chmod +x "${BIN_DIR}/tools/uv/${ORCA_BUNDLED_UV_FILENAME}"
COMMENT "Copying bundled uv into the build tree"
VERBATIM)
endif()
endif ()
# Slic3r binary install target. Default build type is release in case no CMAKE_BUILD_TYPE is provided.
@@ -288,6 +357,7 @@ if (WIN32)
install(TARGETS OrcaSlicer_app_gui RUNTIME DESTINATION ".")
endif ()
install(FILES ${output_dlls_${build_type}} DESTINATION ".")
install(DIRECTORY "${CMAKE_PREFIX_PATH}/libpython/" DESTINATION "python")
else ()
install(TARGETS OrcaSlicer RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR})
endif ()
-1
View File
@@ -51,7 +51,6 @@ using namespace nlohmann;
#include "libslic3r/Config.hpp"
#include "libslic3r/Geometry.hpp"
#include "libslic3r/GCode.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/ModelArrange.hpp"
#include "libslic3r/Platform.hpp"
+8
View File
@@ -263,6 +263,14 @@ int wmain(int argc, wchar_t **argv)
_wsplitpath(path_to_exe, drive, dir, fname, ext);
_wmakepath(path_to_exe, drive, dir, nullptr, nullptr);
wchar_t path_to_python[MAX_PATH + 1] = { 0 };
wcscpy(path_to_python, path_to_exe);
wcscat(path_to_python, L"python");
DWORD python_attrs = GetFileAttributesW(path_to_python);
if (python_attrs != INVALID_FILE_ATTRIBUTES && (python_attrs & FILE_ATTRIBUTE_DIRECTORY)) {
SetDllDirectoryW(path_to_python);
}
#ifdef SLIC3R_GUI
// https://wiki.qt.io/Cross_compiling_Mesa_for_Windows
// http://download.qt.io/development_releases/prebuilt/llvmpipe/windows/
@@ -171,6 +171,33 @@ echo -n "[9/9] Generating Linux app..."
fi
cp -fl "${ORIGINAL_BINARY_LOCATION}" "$BIN_DIR/@SLIC3R_APP_CMD@"
# Bundle the embedded Python runtime (interpreter + stdlib) that CMake staged
# next to the binary. OrcaSlicer resolves PYTHONHOME at <resources_dir>/../lib/python,
# which is $APPDIR/lib/python (resources_dir is $APPDIR/resources). Staging it here,
# before the dependency-closure pass below, lets that pass also bundle the extension
# modules' shared libraries; libpython itself is found via rpath ($ORIGIN entries set
# in src/CMakeLists.txt and by the dep recipe). Without this the plugin system cannot start.
PYTHON_RUNTIME_SRC="$(dirname "${ORIGINAL_BINARY_LOCATION}")/python"
if [ -d "$PYTHON_RUNTIME_SRC" ]; then
echo "Bundling Python runtime from ${PYTHON_RUNTIME_SRC} ..."
copy_directory_if_present "$PYTHON_RUNTIME_SRC" "$LIB_DIR/python"
else
echo "Warning: bundled Python runtime not found at ${PYTHON_RUNTIME_SRC}; plugin support will be disabled in this AppImage."
fi
# Bundle the uv executable (CMake staged it next to the binary) so the plugin
# system can install Python package dependencies. bundled_uv_path() looks under
# <resources_dir>/tools/uv == $APPDIR/resources/tools/uv -- the same layout the
# install()-based packagers (Windows/Flatpak/FHS) use. Unlike the AppImage, those
# run `make install`; this build copies artifacts, so stage uv explicitly.
UV_RUNTIME_SRC="$(dirname "${ORIGINAL_BINARY_LOCATION}")/tools/uv"
if [ -d "$UV_RUNTIME_SRC" ]; then
echo "Bundling uv from ${UV_RUNTIME_SRC} ..."
copy_directory_if_present "$UV_RUNTIME_SRC" "$APPDIR/resources/tools/uv"
else
echo "Warning: bundled uv not found at ${UV_RUNTIME_SRC}; Python package installation will be unavailable in this AppImage."
fi
if [ "$BUNDLE_DESKTOP_STACK" = "1" ]; then
GSTREAMER_PLUGIN_SOURCE_DIR="$(find_pkg_config_dir pluginsdir gstreamer-1.0 || true)"
if [ -z "$GSTREAMER_PLUGIN_SOURCE_DIR" ]; then
+21 -4
View File
@@ -1,11 +1,11 @@
/**
* Loader generated by glad 2.0.8 on Tue Apr 7 00:45:10 2026
* Loader generated by glad 2.0.8 on Thu Jul 16 03:42:05 2026
*
* SPDX-License-Identifier: (WTFPL OR CC0-1.0) AND Apache-2.0
*
* Generator: C/C++
* Specification: gl
* Extensions: 5
* Extensions: 7
*
* APIs:
* - gl:compatibility=4.6
@@ -19,10 +19,10 @@
* - ON_DEMAND = False
*
* Commandline:
* --api='gl:compatibility=4.6' --extensions='GL_ARB_compatibility,GL_ARB_framebuffer_object,GL_EXT_framebuffer_object,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic' c --loader
* --api='gl:compatibility=4.6' --extensions='GL_ARB_compatibility,GL_ARB_framebuffer_object,GL_EXT_framebuffer_blit,GL_EXT_framebuffer_multisample,GL_EXT_framebuffer_object,GL_EXT_texture_compression_s3tc,GL_EXT_texture_filter_anisotropic' c --loader
*
* Online:
* http://glad.sh/#api=gl%3Acompatibility%3D4.6&extensions=GL_ARB_compatibility%2CGL_ARB_framebuffer_object%2CGL_EXT_framebuffer_object%2CGL_EXT_texture_compression_s3tc%2CGL_EXT_texture_filter_anisotropic&generator=c&options=LOADER
* http://glad.sh/#api=gl%3Acompatibility%3D4.6&extensions=GL_ARB_compatibility%2CGL_ARB_framebuffer_object%2CGL_EXT_framebuffer_blit%2CGL_EXT_framebuffer_multisample%2CGL_EXT_framebuffer_object%2CGL_EXT_texture_compression_s3tc%2CGL_EXT_texture_filter_anisotropic&generator=c&options=LOADER
*
*/
@@ -616,6 +616,8 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
#define GL_DRAW_BUFFER9 0x882E
#define GL_DRAW_FRAMEBUFFER 0x8CA9
#define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6
#define GL_DRAW_FRAMEBUFFER_BINDING_EXT 0x8CA6
#define GL_DRAW_FRAMEBUFFER_EXT 0x8CA9
#define GL_DRAW_INDIRECT_BUFFER 0x8F3F
#define GL_DRAW_INDIRECT_BUFFER_BINDING 0x8F43
#define GL_DRAW_PIXEL_TOKEN 0x0705
@@ -746,6 +748,7 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7
#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56
#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC
#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC
#define GL_FRAMEBUFFER_RENDERABLE 0x8289
@@ -1106,6 +1109,7 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
#define GL_MAX_RENDERBUFFER_SIZE 0x84E8
#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8
#define GL_MAX_SAMPLES 0x8D57
#define GL_MAX_SAMPLES_EXT 0x8D57
#define GL_MAX_SAMPLE_MASK_WORDS 0x8E59
#define GL_MAX_SERVER_WAIT_TIMEOUT 0x9111
#define GL_MAX_SHADER_STORAGE_BLOCK_SIZE 0x90DE
@@ -1400,6 +1404,8 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
#define GL_READ_BUFFER 0x0C02
#define GL_READ_FRAMEBUFFER 0x8CA8
#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA
#define GL_READ_FRAMEBUFFER_BINDING_EXT 0x8CAA
#define GL_READ_FRAMEBUFFER_EXT 0x8CA8
#define GL_READ_ONLY 0x88B8
#define GL_READ_PIXELS 0x828C
#define GL_READ_PIXELS_FORMAT 0x828D
@@ -1437,6 +1443,7 @@ typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apipro
#define GL_RENDERBUFFER_RED_SIZE 0x8D50
#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50
#define GL_RENDERBUFFER_SAMPLES 0x8CAB
#define GL_RENDERBUFFER_SAMPLES_EXT 0x8CAB
#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55
#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55
#define GL_RENDERBUFFER_WIDTH 0x8D42
@@ -2150,6 +2157,10 @@ GLAD_API_CALL int GLAD_GL_VERSION_4_6;
GLAD_API_CALL int GLAD_GL_ARB_compatibility;
#define GL_ARB_framebuffer_object 1
GLAD_API_CALL int GLAD_GL_ARB_framebuffer_object;
#define GL_EXT_framebuffer_blit 1
GLAD_API_CALL int GLAD_GL_EXT_framebuffer_blit;
#define GL_EXT_framebuffer_multisample 1
GLAD_API_CALL int GLAD_GL_EXT_framebuffer_multisample;
#define GL_EXT_framebuffer_object 1
GLAD_API_CALL int GLAD_GL_EXT_framebuffer_object;
#define GL_EXT_texture_compression_s3tc 1
@@ -2205,6 +2216,7 @@ typedef void (GLAD_API_PTR *PFNGLBLENDFUNCSEPARATEPROC)(GLenum sfactorRGB, GLenu
typedef void (GLAD_API_PTR *PFNGLBLENDFUNCSEPARATEIPROC)(GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha);
typedef void (GLAD_API_PTR *PFNGLBLENDFUNCIPROC)(GLuint buf, GLenum src, GLenum dst);
typedef void (GLAD_API_PTR *PFNGLBLITFRAMEBUFFERPROC)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
typedef void (GLAD_API_PTR *PFNGLBLITFRAMEBUFFEREXTPROC)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
typedef void (GLAD_API_PTR *PFNGLBLITNAMEDFRAMEBUFFERPROC)(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
typedef void (GLAD_API_PTR *PFNGLBUFFERDATAPROC)(GLenum target, GLsizeiptr size, const void * data, GLenum usage);
typedef void (GLAD_API_PTR *PFNGLBUFFERSTORAGEPROC)(GLenum target, GLsizeiptr size, const void * data, GLbitfield flags);
@@ -2881,6 +2893,7 @@ typedef GLint (GLAD_API_PTR *PFNGLRENDERMODEPROC)(GLenum mode);
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEEXTPROC)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
typedef void (GLAD_API_PTR *PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
typedef void (GLAD_API_PTR *PFNGLRESUMETRANSFORMFEEDBACKPROC)(void);
typedef void (GLAD_API_PTR *PFNGLROTATEDPROC)(GLdouble angle, GLdouble x, GLdouble y, GLdouble z);
typedef void (GLAD_API_PTR *PFNGLROTATEFPROC)(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
@@ -3318,6 +3331,8 @@ GLAD_API_CALL PFNGLBLENDFUNCIPROC glad_glBlendFunci;
#define glBlendFunci glad_glBlendFunci
GLAD_API_CALL PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer;
#define glBlitFramebuffer glad_glBlitFramebuffer
GLAD_API_CALL PFNGLBLITFRAMEBUFFEREXTPROC glad_glBlitFramebufferEXT;
#define glBlitFramebufferEXT glad_glBlitFramebufferEXT
GLAD_API_CALL PFNGLBLITNAMEDFRAMEBUFFERPROC glad_glBlitNamedFramebuffer;
#define glBlitNamedFramebuffer glad_glBlitNamedFramebuffer
GLAD_API_CALL PFNGLBUFFERDATAPROC glad_glBufferData;
@@ -4670,6 +4685,8 @@ GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEEXTPROC glad_glRenderbufferStorageEXT;
#define glRenderbufferStorageEXT glad_glRenderbufferStorageEXT
GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample;
#define glRenderbufferStorageMultisample glad_glRenderbufferStorageMultisample
GLAD_API_CALL PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glad_glRenderbufferStorageMultisampleEXT;
#define glRenderbufferStorageMultisampleEXT glad_glRenderbufferStorageMultisampleEXT
GLAD_API_CALL PFNGLRESUMETRANSFORMFEEDBACKPROC glad_glResumeTransformFeedback;
#define glResumeTransformFeedback glad_glResumeTransformFeedback
GLAD_API_CALL PFNGLROTATEDPROC glad_glRotated;
+22 -1
View File
@@ -44,6 +44,8 @@ int GLAD_GL_VERSION_4_5 = 0;
int GLAD_GL_VERSION_4_6 = 0;
int GLAD_GL_ARB_compatibility = 0;
int GLAD_GL_ARB_framebuffer_object = 0;
int GLAD_GL_EXT_framebuffer_blit = 0;
int GLAD_GL_EXT_framebuffer_multisample = 0;
int GLAD_GL_EXT_framebuffer_object = 0;
int GLAD_GL_EXT_texture_compression_s3tc = 0;
int GLAD_GL_EXT_texture_filter_anisotropic = 0;
@@ -97,6 +99,7 @@ PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate = NULL;
PFNGLBLENDFUNCSEPARATEIPROC glad_glBlendFuncSeparatei = NULL;
PFNGLBLENDFUNCIPROC glad_glBlendFunci = NULL;
PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer = NULL;
PFNGLBLITFRAMEBUFFEREXTPROC glad_glBlitFramebufferEXT = NULL;
PFNGLBLITNAMEDFRAMEBUFFERPROC glad_glBlitNamedFramebuffer = NULL;
PFNGLBUFFERDATAPROC glad_glBufferData = NULL;
PFNGLBUFFERSTORAGEPROC glad_glBufferStorage = NULL;
@@ -773,6 +776,7 @@ PFNGLRENDERMODEPROC glad_glRenderMode = NULL;
PFNGLRENDERBUFFERSTORAGEPROC glad_glRenderbufferStorage = NULL;
PFNGLRENDERBUFFERSTORAGEEXTPROC glad_glRenderbufferStorageEXT = NULL;
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glRenderbufferStorageMultisample = NULL;
PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC glad_glRenderbufferStorageMultisampleEXT = NULL;
PFNGLRESUMETRANSFORMFEEDBACKPROC glad_glResumeTransformFeedback = NULL;
PFNGLROTATEDPROC glad_glRotated = NULL;
PFNGLROTATEFPROC glad_glRotatef = NULL;
@@ -2249,6 +2253,14 @@ static void glad_gl_load_GL_ARB_framebuffer_object( GLADuserptrloadfunc load, vo
glad_glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC) load(userptr, "glRenderbufferStorage");
glad_glRenderbufferStorageMultisample = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC) load(userptr, "glRenderbufferStorageMultisample");
}
static void glad_gl_load_GL_EXT_framebuffer_blit( GLADuserptrloadfunc load, void* userptr) {
if(!GLAD_GL_EXT_framebuffer_blit) return;
glad_glBlitFramebufferEXT = (PFNGLBLITFRAMEBUFFEREXTPROC) load(userptr, "glBlitFramebufferEXT");
}
static void glad_gl_load_GL_EXT_framebuffer_multisample( GLADuserptrloadfunc load, void* userptr) {
if(!GLAD_GL_EXT_framebuffer_multisample) return;
glad_glRenderbufferStorageMultisampleEXT = (PFNGLRENDERBUFFERSTORAGEMULTISAMPLEEXTPROC) load(userptr, "glRenderbufferStorageMultisampleEXT");
}
static void glad_gl_load_GL_EXT_framebuffer_object( GLADuserptrloadfunc load, void* userptr) {
if(!GLAD_GL_EXT_framebuffer_object) return;
glad_glBindFramebufferEXT = (PFNGLBINDFRAMEBUFFEREXTPROC) load(userptr, "glBindFramebufferEXT");
@@ -2366,6 +2378,8 @@ static int glad_gl_find_extensions_gl(void) {
GLAD_GL_ARB_compatibility = glad_gl_has_extension(exts, exts_i, "GL_ARB_compatibility");
GLAD_GL_ARB_framebuffer_object = glad_gl_has_extension(exts, exts_i, "GL_ARB_framebuffer_object");
GLAD_GL_EXT_framebuffer_blit = glad_gl_has_extension(exts, exts_i, "GL_EXT_framebuffer_blit");
GLAD_GL_EXT_framebuffer_multisample = glad_gl_has_extension(exts, exts_i, "GL_EXT_framebuffer_multisample");
GLAD_GL_EXT_framebuffer_object = glad_gl_has_extension(exts, exts_i, "GL_EXT_framebuffer_object");
GLAD_GL_EXT_texture_compression_s3tc = glad_gl_has_extension(exts, exts_i, "GL_EXT_texture_compression_s3tc");
GLAD_GL_EXT_texture_filter_anisotropic = glad_gl_has_extension(exts, exts_i, "GL_EXT_texture_filter_anisotropic");
@@ -2451,6 +2465,8 @@ int gladLoadGLUserPtr( GLADuserptrloadfunc load, void *userptr) {
if (!glad_gl_find_extensions_gl()) return 0;
glad_gl_load_GL_ARB_framebuffer_object(load, userptr);
glad_gl_load_GL_EXT_framebuffer_blit(load, userptr);
glad_gl_load_GL_EXT_framebuffer_multisample(load, userptr);
glad_gl_load_GL_EXT_framebuffer_object(load, userptr);
@@ -2574,7 +2590,9 @@ static void* glad_gl_dlopen_handle(void) {
"libGL-1.so",
#endif
"libGL.so.1",
"libGL.so"
"libGL.so",
"libEGL.so.1",
"libEGL.so"
};
#endif
@@ -2597,6 +2615,9 @@ static struct _glad_gl_userptr glad_gl_build_userptr(void *handle) {
#else
userptr.gl_get_proc_address_ptr =
(GLADglprocaddrfunc) glad_dlsym_handle(handle, "glXGetProcAddressARB");
if (!userptr.gl_get_proc_address_ptr)
userptr.gl_get_proc_address_ptr =
(GLADglprocaddrfunc) glad_dlsym_handle(handle, "eglGetProcAddress");
#endif
return userptr;
+4
View File
@@ -202,6 +202,10 @@ void AppConfig::set_defaults()
if (get("seq_top_layer_only").empty())
set("seq_top_layer_only", "1");
// ORCA: darken layers below the current one while scrubbing the preview (ported from preFlight)
if (get("preview_dim_previous_layers").empty())
set_bool("preview_dim_previous_layers", false);
if (get("filaments_area_preferred_count").empty())
set("filaments_area_preferred_count", "10");
+1 -1
View File
@@ -125,7 +125,7 @@ public:
template<class It, class = IteratorOnly<It> > BoundingBox3Base(It from, It to)
{
if (from == to)
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor.");
auto it = from;
this->min = it->template cast<typename PointType::Scalar>();
+43 -129
View File
@@ -34,14 +34,14 @@ static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const P
}
// BBS: generate brim area by objs
static void append_and_translate(ExPolygons& dst, const ExPolygons& src,
const PrintInstance& instance, const Print& print, std::map<ObjectID, ExPolygons>& brimAreaMap) {
const PrintInstance& instance, size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
ExPolygons srcShifted = src;
Point instance_shift = instance.shift_without_plate_offset();
for (size_t src_idx = 0; src_idx < srcShifted.size(); ++src_idx)
srcShifted[src_idx].translate(instance_shift);
srcShifted = diff_ex(srcShifted, dst);
//expolygons_append(dst, temp2);
expolygons_append(brimAreaMap[instance.print_object->id()], std::move(srcShifted));
expolygons_append(brimAreaMap[{ instance.print_object->id(), instance_idx }], std::move(srcShifted));
}
static void append_and_translate(Polygons &dst, const Polygons &src, const PrintInstance &instance) {
@@ -420,8 +420,7 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi
//BBS: create all brims
static ExPolygons outer_inner_brim_area(const Print& print,
const float no_brim_offset, std::map<ObjectID, ExPolygons>& brimAreaMap,
std::map<ObjectID, ExPolygons>& supportBrimAreaMap,
const float no_brim_offset, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap,
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
std::vector<unsigned int>& printExtruders)
{
@@ -470,7 +469,6 @@ static ExPolygons outer_inner_brim_area(const Print& print,
ExPolygons brim_area_object;
ExPolygons no_brim_area_object;
ExPolygons brim_area_support;
ExPolygons no_brim_area_support;
Polygons holes_object;
Polygons holes_support;
@@ -571,16 +569,18 @@ static ExPolygons outer_inner_brim_area(const Print& print,
append(no_brim_area_object, objectIsland);
brimToWrite.at(object->id()).obj = false;
for (const PrintInstance& instance : object->instances()) {
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
const PrintInstance& instance = object->instances()[instance_idx];
if (!brim_area_object.empty())
append_and_translate(brim_area, brim_area_object, instance, print, brimAreaMap);
append_and_translate(brim_area, brim_area_object, instance, instance_idx, brimAreaMap);
append_and_translate(no_brim_area, no_brim_area_object, instance);
append_and_translate(holes, holes_object, instance);
append_and_translate(objectIslands, objectIsland, instance);
}
if (brimAreaMap.find(object->id()) != brimAreaMap.end())
expolygons_append(brim_area, brimAreaMap[object->id()]);
for (const auto& [key, areas] : brimAreaMap)
if (key.object_id == object->id())
expolygons_append(brim_area, areas);
}
support_material_extruder = object->config().support_filament;
if (support_material_extruder == 0 && object->has_support_material()) {
@@ -592,32 +592,12 @@ static ExPolygons outer_inner_brim_area(const Print& print,
if (support_material_extruder == extruderNo && brimToWrite.at(object->id()).sup) {
if (!object->support_layers().empty() && object->support_layers().front()->support_type==stInnerNormal) {
for (const Polygon& support_contour : object->support_layers().front()->support_fills.polygons_covered_by_spacing()) {
// Brim will not be generated for supports
/*
if (has_outer_brim) {
append(brim_area_support, diff_ex(offset_ex(support_contour, brim_width + brim_offset, jtRound, SCALED_RESOLUTION), offset_ex(support_contour, brim_offset)));
}
if (has_inner_brim || has_outer_brim)
append(no_brim_area_support, offset_ex(support_contour, 0));
*/
no_brim_area_support.emplace_back(support_contour);
}
}
// BBS
if (!object->support_layers().empty() && object->support_layers().front()->support_type == stInnerTree) {
for (const ExPolygon &ex_poly : object->support_layers().front()->lslices) {
// BBS: additional brim width will be added if adhesion area is too small without brim
float brim_width_mod = ex_poly.area() / ex_poly.contour.length() < scaled_half_min_adh_length
&& brim_width < scaled_flow_width ? brim_width + scaled_additional_brim_width : brim_width;
brim_width_mod = floor(brim_width_mod / scaled_flow_width / 2) * scaled_flow_width * 2;
// Brim will not be generated for supports
/*
if (has_outer_brim) {
append(brim_area_support, diff_ex(offset_ex(ex_poly.contour, brim_width_mod + brim_offset, jtRound, SCALED_RESOLUTION), offset_ex(ex_poly.contour, brim_offset)));
}
if (has_inner_brim)
append(brim_area_support, diff_ex(offset_ex(ex_poly.holes, -brim_offset), offset_ex(ex_poly.holes, -brim_width - brim_offset)));
*/
if (!has_outer_brim)
append(no_brim_area_support, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes));
if (!has_inner_brim && !has_outer_brim)
@@ -630,13 +610,9 @@ static ExPolygons outer_inner_brim_area(const Print& print,
}
brimToWrite.at(object->id()).sup = false;
for (const PrintInstance& instance : object->instances()) {
if (!brim_area_support.empty())
append_and_translate(brim_area, brim_area_support, instance, print, supportBrimAreaMap);
append_and_translate(no_brim_area, no_brim_area_support, instance);
append_and_translate(holes, holes_support, instance);
}
if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end())
expolygons_append(brim_area, supportBrimAreaMap[object->id()]);
}
}
}
@@ -676,28 +652,27 @@ static ExPolygons outer_inner_brim_area(const Print& print,
}
if (brimAreaMap.find(object->id()) != brimAreaMap.end()) {
brimAreaMap[object->id()] = diff_ex(brimAreaMap[object->id()], extruder_no_brim_area);
}
for (auto& [key, areas] : brimAreaMap)
if (key.object_id == object->id())
areas = diff_ex(areas, extruder_no_brim_area);
if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end())
supportBrimAreaMap[object->id()] = diff_ex(supportBrimAreaMap[object->id()], extruder_no_brim_area);
}
brim_area.clear();
for (const PrintObject* object : print.objects()) {
// BBS: brim should be contacted to at least one object's island or brim area
if (brimAreaMap.find(object->id()) != brimAreaMap.end()) {
for (auto map_it = brimAreaMap.begin(); map_it != brimAreaMap.end(); ++map_it) {
if (map_it->first.object_id != object->id())
continue;
// find other objects' brim area
ExPolygons otherExPolys;
for (const PrintObject* otherObject : print.objects()) {
if ((otherObject->id() != object->id()) && (brimAreaMap.find(otherObject->id()) != brimAreaMap.end())) {
expolygons_append(otherExPolys, brimAreaMap[otherObject->id()]);
}
}
for (const auto& [other_key, other_areas] : brimAreaMap)
if (other_key != map_it->first)
expolygons_append(otherExPolys, other_areas);
auto tempArea = brimAreaMap[object->id()];
brimAreaMap[object->id()].clear();
auto tempArea = map_it->second;
map_it->second.clear();
for (int ia = 0; ia != tempArea.size(); ++ia) {
// find this object's other brim area
@@ -709,9 +684,9 @@ static ExPolygons outer_inner_brim_area(const Print& print,
if (!intersection_ex(offsetedTa, objectIslands).empty() ||
!intersection_ex(offsetedTa, otherExPoly).empty() ||
!intersection_ex(offsetedTa, otherExPolys).empty())
brimAreaMap[object->id()].push_back(tempArea[ia]);
map_it->second.push_back(tempArea[ia]);
}
expolygons_append(brim_area, brimAreaMap[object->id()]);
expolygons_append(brim_area, map_it->second);
}
}
return brim_area;
@@ -890,19 +865,15 @@ ExtrusionEntityCollection makeBrimInfillFromPlateCoordinates(const ExPolygons& s
//BBS: an overload of the orignal brim generator that generates the brim by obj and by extruders
void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_area,
std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
std::map<ObjectInstanceID, ExtrusionEntityCollection>& brimMapByInstance,
std::vector<std::pair<ObjectID, unsigned int>> &objPrintVec,
std::vector<unsigned int>& printExtruders,
std::map<ObjectID, ExPolygons>* objectBrimAreasOut,
std::map<ObjectID, ExPolygons>* supportBrimAreasOut)
std::map<ObjectInstanceID, ExPolygons>* objectBrimAreasByInstanceOut)
{
std::map<ObjectID, double> brim_width_map;
std::map<ObjectID, ExPolygons> brimAreaMap;
std::map<ObjectID, ExPolygons> supportBrimAreaMap;
std::map<ObjectInstanceID, ExPolygons> brimAreaMap;
Flow flow = print.brim_flow();
const auto scaled_resolution = scaled<double>(print.config().resolution.value);
ExPolygons islands_area_ex = outer_inner_brim_area(print,
float(flow.scaled_spacing()), brimAreaMap, supportBrimAreaMap, objPrintVec, printExtruders);
float(flow.scaled_spacing()), brimAreaMap, objPrintVec, printExtruders);
// BBS: Find boundingbox of the first layer
for (const ObjectID printObjID : print.print_object_ids()) {
@@ -924,14 +895,10 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
ex_poly_translated.translate(instance.shift_without_plate_offset());
bbx.merge(get_extents(ex_poly_translated));
}
if (supportBrimAreaMap.find(printObjID) != supportBrimAreaMap.end()) {
for (const ExPolygon& ex_poly : supportBrimAreaMap.at(printObjID))
bbx.merge(get_extents(ex_poly.contour));
}
if (brimAreaMap.find(printObjID) != brimAreaMap.end()) {
for (const ExPolygon& ex_poly : brimAreaMap.at(printObjID))
bbx.merge(get_extents(ex_poly.contour));
}
for (const auto& [key, areas] : brimAreaMap)
if (key.object_id == printObjID)
for (const ExPolygon& ex_poly : areas)
bbx.merge(get_extents(ex_poly.contour));
object->firstLayerObjectBrimBoundingBox = bbx;
}
@@ -944,77 +911,24 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
islands_area[iia].translate(plate_shift);
// Orca: keep translated brim footprints for skirt grouping.
auto translate_area_map = [plate_shift](const std::map<ObjectID, ExPolygons>& src) {
std::map<ObjectID, ExPolygons> dst = src;
auto translate_area_map = [plate_shift](const auto& src) {
auto dst = src;
for (auto& [_, areas] : dst)
for (ExPolygon& area : areas)
area.translate(plate_shift);
return dst;
};
if (objectBrimAreasOut != nullptr)
*objectBrimAreasOut = translate_area_map(brimAreaMap);
if (supportBrimAreasOut != nullptr)
*supportBrimAreasOut = translate_area_map(supportBrimAreaMap);
if (objectBrimAreasByInstanceOut != nullptr)
*objectBrimAreasByInstanceOut = translate_area_map(brimAreaMap);
const bool has_per_object_skirt_or_shield = print.config().skirt_type == stPerObject &&
(print.has_skirt() || print.has_infinite_skirt());
const bool combine_brims = print.config().combine_brims.value &&
!has_per_object_skirt_or_shield &&
print.config().print_sequence != PrintSequence::ByObject;
if (!combine_brims) {
// Orca: Generate brims separately when brims cannot be combined.
for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) {
if (!iter->second.empty()) {
brimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
};
}
for (auto iter = supportBrimAreaMap.begin(); iter != supportBrimAreaMap.end(); ++iter) {
if (!iter->second.empty()) {
supportBrimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area)));
};
}
} else {
// Orca: Unified brim mode.
ExPolygons all_brims_merged;
std::vector<ObjectID> brim_object_ids;
// Add all object brims
for (auto& [obj_id, brims] : brimAreaMap) {
if (!brims.empty()) {
expolygons_append(all_brims_merged, brims);
brim_object_ids.push_back(obj_id);
}
}
if (!all_brims_merged.empty()) {
// Merge all brims into a single continuous area
all_brims_merged = union_ex(all_brims_merged);
// Apply a tiny morphological cleanup to reduce boolean-union micro-artifacts.
const float brim_cleanup_delta = std::max(float(scaled_resolution), float(SCALED_EPSILON));
all_brims_merged = offset2_ex(all_brims_merged, brim_cleanup_delta, -brim_cleanup_delta, jtRound, scaled_resolution);
// Generate infill once for the merged brim area.
ExtrusionEntityCollection merged_brim = makeBrimInfill(all_brims_merged, print, islands_area);
// In unified mode, assign the merged brim to a deterministic carrier object.
// Pick the first object in print order that actually contributed brim area.
ObjectID carrier_id;
bool carrier_found = false;
for (const auto& [obj_id, _extruder] : objPrintVec) {
if (std::find(brim_object_ids.begin(), brim_object_ids.end(), obj_id) != brim_object_ids.end()) {
carrier_id = obj_id;
carrier_found = true;
break;
}
}
if (!carrier_found)
carrier_id = brim_object_ids.front();
brimMap[carrier_id] = std::move(merged_brim);
}
// Orca: Generate brims per object instance. If Combine brims is enabled,
// Print::_make_skirt() will join the touching ones.
for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) {
if (!iter->second.empty()) {
ExtrusionEntityCollection brim = makeBrimInfill(iter->second, print, islands_area);
brimMap[iter->first.object_id].append(brim.entities);
brimMapByInstance.emplace(iter->first, std::move(brim));
};
}
}
} // namespace Slic3r
+3 -4
View File
@@ -2,6 +2,7 @@
#define slic3r_Brim_hpp_
#include "ExPolygon.hpp"
#include "ObjectID.hpp"
#include "Point.hpp"
#include<map>
@@ -12,17 +13,15 @@ namespace Slic3r {
class Print;
class ExtrusionEntityCollection;
class PrintTryCancel;
class ObjectID;
// Produce brim lines around those objects, that have the brim enabled.
// Collect islands_area to be merged into the final 1st layer convex hull.
void make_brim(const Print& print, PrintTryCancel try_cancel,
Polygons& islands_area, std::map<ObjectID, ExtrusionEntityCollection>& brimMap,
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
std::map<ObjectInstanceID, ExtrusionEntityCollection>& brimMapByInstance,
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
std::vector<unsigned int>& printExtruders,
std::map<ObjectID, ExPolygons>* objectBrimAreasOut = nullptr,
std::map<ObjectID, ExPolygons>* supportBrimAreasOut = nullptr);
std::map<ObjectInstanceID, ExPolygons>* objectBrimAreasByInstanceOut = nullptr);
ExtrusionEntityCollection makeBrimInfill(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area);
ExtrusionEntityCollection makeBrimInfillFromPlateCoordinates(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area);
-2
View File
@@ -225,8 +225,6 @@ set(lisbslic3r_sources
GCode.hpp
GCode/PchipInterpolatorHelper.cpp
GCode/PchipInterpolatorHelper.hpp
GCode/PostProcessor.cpp
GCode/PostProcessor.hpp
GCode/PressureEqualizer.cpp
GCode/PressureEqualizer.hpp
GCode/PrintExtents.cpp
+104 -3
View File
@@ -4,6 +4,7 @@
#include "LocalesUtils.hpp"
#include "Preset.hpp"
#include <algorithm>
#include <assert.h>
#include <fstream>
#include <iostream>
@@ -36,6 +37,8 @@ using namespace nlohmann;
namespace Slic3r {
std::function<std::string(std::string, std::string)> ConfigBase::resolve_capability_fn = nullptr;
//BBS: add json support
//static const std::string CONFIG_VERSION_KEY = "version";
//static const std::string CONFIG_NAME_KEY = "name";
@@ -84,12 +87,12 @@ std::string escape_strings_cstyle(const std::vector<std::string> &strs)
// Separate the strings.
(*outptr ++) = ';';
const std::string &str = strs[j];
// Is the string simple or complex? Complex string contains spaces, tabs, new lines and other
// escapable characters. Empty string shall be quoted as well, if it is the only string in strs.
// Is the string simple or complex? Complex string contains spaces, tabs, semicolons, new lines
// and other escapable characters. Empty string shall be quoted as well, if it is the only string in strs.
bool should_quote = strs.size() == 1 && str.empty();
for (size_t i = 0; i < str.size(); ++ i) {
char c = str[i];
if (c == ' ' || c == '\t' || c == '\\' || c == '"' || c == '\r' || c == '\n') {
if (c == ' ' || c == '\t' || c == ';' || c == '\\' || c == '"' || c == '\r' || c == '\n') {
should_quote = true;
break;
}
@@ -1486,6 +1489,29 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, Fo
return std::move(substitutions_ctxt.substitutions);
}
std::optional<PluginCapabilityRef> parse_capability_ref(const std::string& value)
{
// Capability references are stored as "<plugin_name>;<cloud_uuid>;<capability_name>".
// The cloud UUID is empty for local plugins (two consecutive semicolons).
if (value.empty())
return std::nullopt;
const size_t first = value.find(';');
if (first == std::string::npos)
return std::nullopt;
const size_t second = value.find(';', first + 1);
if (second == std::string::npos)
return std::nullopt;
std::string name = value.substr(0, first);
std::string uuid = value.substr(first + 1, second - first - 1);
std::string capability_name = value.substr(second + 1);
if (name.empty() || capability_name.empty())
return std::nullopt;
return PluginCapabilityRef{ std::move(name), std::move(capability_name), std::move(uuid) };
}
//BBS: add json support
void ConfigBase::save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const
{
@@ -1522,6 +1548,18 @@ void ConfigBase::save_to_json(const std::string &file, const std::string &name,
}
}
// Serialize the top-level "plugins" manifest: the individual plugin-backed options keep bare
// capability names; the full "name;uuid;capability" references are derived here (same helper as
// update_plugin_manifest). Only with a resolver (GUI); without one (CLI/headless) leave whatever
// the "plugins" option already serialized above, so a round-trip never drops the manifest.
if (resolve_capability_fn) {
std::vector<std::string> unique_refs = this->collect_plugin_manifest();
if (unique_refs.empty())
j.erase("plugins");
else
j["plugins"] = unique_refs;
}
boost::nowide::ofstream c;
c.open(file, std::ios::out | std::ios::trunc);
c << j.dump(1, '\t') << std::endl;
@@ -1553,6 +1591,69 @@ void ConfigBase::null_nullables()
}
}
void ConfigBase::save_plugin_collection(const std::string& opt_key, const ConfigOption* opt, std::vector<std::string>& plugin_refs) const {
// Full plugin capability references ("name;uuid;capability") can only be derived through the
// resolver registered by the GUI once plugins are loaded. In non-GUI/headless contexts (e.g.
// the CLI) it stays null, so skip silently rather than calling an empty std::function.
if (!resolve_capability_fn)
return;
// A plugin-backed option declares its capability type via ConfigOptionDef::plugin_type (the same
// metadata PluginResolver::find_option_for_capability scans). Deriving off the def rather than a
// per-key branch keeps this generic across every plugin-backed option.
const ConfigDef* def = this->def();
const ConfigOptionDef* opt_def = def ? def->get(opt_key) : nullptr;
if (opt_def == nullptr || !opt_def->is_plugin_backed())
return;
const std::string& type = opt_def->plugin_type;
// Resolve a single bare capability value into its full reference and append it, skipping unset
// values, capabilities that could not be resolved (resolver returns ""), and duplicates already
// collected (preserving insertion order).
const auto append_ref = [&plugin_refs, &type](const std::string& capability_value) {
if (capability_value.empty())
return;
std::string ref = resolve_capability_fn(capability_value, type);
if (!ref.empty() && std::find(plugin_refs.begin(), plugin_refs.end(), ref) == plugin_refs.end())
plugin_refs.emplace_back(std::move(ref));
};
// Scalar options carry a single capability name; vector options carry a list. Same scalar/vector
// dispatch as PluginResolver::find_option_for_capability.
if (const auto* string_option = dynamic_cast<const ConfigOptionString*>(opt))
append_ref(string_option->value);
else if (const auto* vector_option = dynamic_cast<const ConfigOptionVectorBase*>(opt))
for (const std::string& val : vector_option->vserialize())
append_ref(val);
}
std::vector<std::string> ConfigBase::collect_plugin_manifest() const
{
std::vector<std::string> refs;
if (!resolve_capability_fn)
return refs;
// Each plugin-backed option (ConfigOptionDef::is_plugin_backed) contributes its resolved
// reference(s) via save_plugin_collection, which appends in order and skips duplicates, so no
// second de-duplication pass is needed here.
for (const std::string& opt_key : this->keys())
if (const ConfigOption* opt = this->option(opt_key))
this->save_plugin_collection(opt_key, opt, refs);
return refs;
}
void ConfigBase::update_plugin_manifest()
{
// Writes the derived manifest back into this config's "plugins" option (save_to_json writes the
// same manifest into a JSON document instead), so an in-memory backend config carries a resolved
// manifest even when the source preset was never serialized (picked-but-unsaved). Without a
// resolver (CLI/headless) leave whatever manifest was loaded from disk untouched.
if (!resolve_capability_fn)
return;
if (auto* manifest = this->option<ConfigOptionStrings>("plugins", true))
manifest->values = this->collect_plugin_manifest();
}
DynamicConfig::DynamicConfig(const ConfigBase& rhs, const t_config_option_keys& keys)
{
for (const t_config_option_key& opt_key : keys)
+36 -1
View File
@@ -9,6 +9,7 @@
#include <cstdlib>
#include <functional>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
@@ -2254,6 +2255,9 @@ public:
legend,
// Vector value, but edited as a single string.
one_string,
plugin_picker,
// Raw JSON string value, edited through a dialog behind a button rather than in the row.
plugin_config,
};
// Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map.
@@ -2470,6 +2474,13 @@ public:
// "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon.
// "show_value" - even if enum_values / enum_labels are set, still display the value, not the enum label.
std::string gui_flags;
// Capability type of a plugin-backed option, e.g. "slicing-pipeline" / "printer-connection"
// (empty for ordinary options). GUIType::plugin_picker filters the plugin list by it, and it
// resolves the option's "plugins" manifest reference; see is_plugin_backed().
std::string plugin_type;
// Whether this option holds plugin capability name(s) that feed the "plugins" manifest -- true
// iff it declares a plugin_type. Setting plugin_type is the only step needed to add one.
bool is_plugin_backed() const { return !plugin_type.empty(); }
// Label of the GUI input field.
// In case the GUI input fields are grouped in some views, the label defines a short label of a grouped value,
// while full_label contains a label of a stand-alone field.
@@ -2783,14 +2794,29 @@ public:
//BBS: add json support
void save_to_json(const std::string &file, const std::string &name, const std::string &from, const std::string &version) const;
// Rebuild the in-memory "plugins" manifest (the "name;uuid;capability" references the plugin
// dispatchers consume) from the plugin-backed options via the registered resolver. save_to_json()
// derives the same manifest, but only when a preset is written to disk; a config assembled in
// memory for the backend (PresetBundle::full_config -> Print::apply) must refresh it here or a
// picked-but-unsaved plugin never resolves at slice/export time. No-op without a resolver.
void update_plugin_manifest();
// Set all the nullable values to nils.
void null_nullables();
static size_t load_from_gcode_string_legacy(ConfigBase& config, const char* str, ConfigSubstitutionContext& substitutions);
static void set_resolve_capability_fn(std::function<std::string(std::string, std::string)> fn) { resolve_capability_fn = fn; }
private:
// Set a configuration value from a string.
bool set_deserialize_raw(const t_config_option_key& opt_key_src, const std::string& value, ConfigSubstitutionContext& substitutions, bool append);
void save_plugin_collection(const std::string& opt_key, const ConfigOption* opt, std::vector<std::string>& plugin_refs) const;
// Collect the de-duplicated "name;uuid;capability" plugin references derived from this config's
// plugin-backed options via the resolver. Shared by save_to_json (serializes them into the JSON
// manifest) and update_plugin_manifest (writes them back into the "plugins" option). Order is
// preserved and empties are dropped; returns empty without a resolver (CLI/headless).
std::vector<std::string> collect_plugin_manifest() const;
static std::function<std::string(std::string, std::string)> resolve_capability_fn;
};
// Configuration store with dynamic number of configuration values.
@@ -3027,6 +3053,15 @@ protected:
void set_defaults();
};
struct PluginCapabilityRef
{
std::string name;
std::string capability_name;
std::string uuid;
};
std::optional<PluginCapabilityRef> parse_capability_ref(const std::string& value);
}
#endif
+3 -3
View File
@@ -61,7 +61,7 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key)
{
throw FlowErrorMissingVariable((boost::format(L("Failed to calculate line width of %1%. Cannot get value of \u201c%2%\u201d ")) % opt_key % dependent_opt_key).str());
throw FlowErrorMissingVariable((boost::format("Failed to calculate line width of %1%. Cannot get value of \u201c%2%\u201d.") % opt_key % dependent_opt_key).str());
}
// Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
@@ -129,7 +129,7 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResol
Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height)
{
if (height <= 0)
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width().");
float w;
if (!width.percent && width.value <= 0.) {
@@ -157,7 +157,7 @@ Flow Flow::with_spacing(float new_spacing) const
assert(m_width >= m_height);
out.m_width += new_spacing - m_spacing;
if (out.m_width < out.m_height)
throw Slic3r::InvalidArgument(L("Invalid spacing supplied to Flow::with_spacing(), check your layer height and extrusion width"));
throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing(), check your layer height and extrusion width.");
}
out.m_spacing = new_spacing;
return out;
+56 -54
View File
@@ -1845,7 +1845,7 @@ static std::vector<Vec2d> get_path_of_change_filament(const Print& print)
std::string WipeTowerIntegration::finalize(GCode &gcodegen)
{
std::string gcode;
if (gcodegen.wipe_tower_type() == WipeTowerType::Type2) {
if (gcodegen.wipe_tower_type() == WipeTowerType::Type2 && !m_final_purge.gcode.empty()) {
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
gcode += gcodegen.change_layer(m_final_purge.print_z);
gcode += append_tcr2(gcodegen, m_final_purge, -1);
@@ -3246,7 +3246,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
BoundingBoxf bbox;
auto pts = std::make_unique<ConfigOptionPoints>();
if (print.calib_mode() == CalibMode::Calib_PA_Line || print.calib_mode() == CalibMode::Calib_PA_Pattern) {
if (print.calib_mode() == CalibMode::Calib_PA_Pattern) {
//PA_Pattern can have any size or arrangement - not dependent on 3mf model size
bbox = bbox_bed;
bbox.offset(-25.0);
// add 4 corner points of bbox into pts
@@ -3256,6 +3257,22 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
pts->values.emplace_back(bbox.max.x(), bbox.max.y());
pts->values.emplace_back(bbox.min.x(), bbox.max.y());
} else if (print.calib_mode() == CalibMode::Calib_PA_Line) {
// Derive X bounds from the actual calibration geometry.
CalibPressureAdvanceLine temp_pa_line_forsize(this);
BoundingBoxf pattern_extents = temp_pa_line_forsize.print_extents(bbox_bed);
bbox = bbox_bed;
bbox.offset(-25.0);
bbox.min.x() = std::max(pattern_extents.min.x(), bbox.min.x());
bbox.max.x() = std::min(pattern_extents.max.x(), bbox.max.x());
pts->values.reserve(4);
pts->values.emplace_back(bbox.min.x(), bbox.min.y());
pts->values.emplace_back(bbox.max.x(), bbox.min.y());
pts->values.emplace_back(bbox.max.x(), bbox.max.y());
pts->values.emplace_back(bbox.min.x(), bbox.max.y());
} else {
// Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
@@ -3591,19 +3608,19 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
}
this->m_objsWithBrim.clear();
this->m_objSupportsWithBrim.clear();
m_brim_done = false;
// BBS: set that indicates objs with brim
for (auto iter = print.m_brimMap.begin(); iter != print.m_brimMap.end(); ++iter) {
if (!iter->second.empty())
this->m_objsWithBrim.insert(iter->first);
// Orca: Track brims by instance. When a combined brim is printed, all of
// its instances are marked done together.
for (const Print::SkirtBrimGroup& group : print.skirt_brim_groups()) {
for (const Print::SkirtBrimGroup::Brim& brim : group.brims) {
if (brim.brim.empty())
continue;
for (const ObjectInstanceID& instance : brim.instances)
this->m_objsWithBrim.insert(instance);
}
}
for (auto iter = print.m_supportBrimMap.begin(); iter != print.m_supportBrimMap.end(); ++iter) {
if (!iter->second.empty())
this->m_objSupportsWithBrim.insert(iter->first);
}
if (this->m_objsWithBrim.empty() && this->m_objSupportsWithBrim.empty()) m_brim_done = true;
if (this->m_objsWithBrim.empty()) m_brim_done = true;
// SoftFever: calib
if (print.calib_params().mode == CalibMode::Calib_PA_Line) {
@@ -4900,25 +4917,30 @@ std::string GCode::generate_skirt(const Print &print,
return gcode;
}
static size_t find_skirt_brim_group_idx(const Print& print, ObjectID object_id)
static size_t find_skirt_brim_group_idx(const Print& print, ObjectID object_id, size_t instance_id)
{
const std::vector<Print::SkirtBrimGroup>& groups = print.skirt_brim_groups();
for (size_t idx = 0; idx < groups.size(); ++idx)
if (std::find(groups[idx].object_ids.begin(), groups[idx].object_ids.end(), object_id) != groups[idx].object_ids.end())
for (size_t idx = 0; idx < groups.size(); ++idx) {
const std::vector<ObjectInstanceID>& instances = groups[idx].instances;
if (std::any_of(instances.begin(), instances.end(), [object_id, instance_id](const ObjectInstanceID& instance) {
return instance.object_id == object_id && instance.instance_id == instance_id;
}))
return idx;
}
return size_t(-1);
}
std::string GCode::generate_object_skirt_group(const Print &print,
const PrintObject &object,
size_t instance_id,
const LayerTools &layer_tools,
const Layer& layer,
unsigned int extruder_id)
{
if (print.config().skirt_type != stPerObject || print.skirt_brim_groups().empty())
if (print.config().skirt_type != stPerObject || !layer_tools.has_skirt || print.skirt_brim_groups().empty())
return {};
const size_t group_idx = find_skirt_brim_group_idx(print, object.id());
const size_t group_idx = find_skirt_brim_group_idx(print, object.id(), instance_id);
if (group_idx == size_t(-1) || print.skirt_brim_groups()[group_idx].skirt.empty())
return {};
@@ -4930,15 +4952,15 @@ std::string GCode::generate_object_skirt_group(const Print &print,
object_skirt_tools, layer, extruder_id, m_skirt_group_done[group_idx]);
}
std::string GCode::generate_object_brim(const Print &print, const PrintObject &object, bool first_layer)
std::string GCode::generate_object_brim(const Print &print, const PrintObject &object, size_t instance_id, bool first_layer)
{
if (!first_layer)
return {};
auto emit_brim = [this](const ExtrusionEntityCollection& brim, const std::vector<ObjectID>& object_ids) {
auto emit_brim = [this](const ExtrusionEntityCollection& brim, const std::vector<ObjectInstanceID>& instances) {
std::string gcode;
const bool already_emitted = std::none_of(object_ids.begin(), object_ids.end(), [this](ObjectID object_id) {
return m_objsWithBrim.find(object_id) != m_objsWithBrim.end();
const bool already_emitted = std::none_of(instances.begin(), instances.end(), [this](const ObjectInstanceID& instance) {
return m_objsWithBrim.find(instance) != m_objsWithBrim.end();
});
if (already_emitted || brim.empty())
return gcode;
@@ -4950,32 +4972,22 @@ std::string GCode::generate_object_brim(const Print &print, const PrintObject &o
gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed));
m_avoid_crossing_perimeters.use_external_mp(false);
m_avoid_crossing_perimeters.disable_once();
for (ObjectID object_id : object_ids)
m_objsWithBrim.erase(object_id);
for (const ObjectInstanceID& instance : instances)
m_objsWithBrim.erase(instance);
return gcode;
};
const bool has_per_object_skirt_or_shield = print.config().skirt_type == stPerObject &&
(print.has_skirt() || print.has_infinite_skirt());
if (print.config().combine_brims && !has_per_object_skirt_or_shield &&
print.config().print_sequence != PrintSequence::ByObject && print.m_brimMap.size() == 1) {
const auto brim_it = print.m_brimMap.begin();
return emit_brim(brim_it->second, { brim_it->first });
}
const size_t group_idx = find_skirt_brim_group_idx(print, object.id());
const ObjectInstanceID object_instance_id{ object.id(), instance_id };
const size_t group_idx = find_skirt_brim_group_idx(print, object.id(), instance_id);
if (group_idx != size_t(-1)) {
std::string gcode;
for (const Print::SkirtBrimGroup::Brim& brim : print.skirt_brim_groups()[group_idx].brims)
if (std::find(brim.object_ids.begin(), brim.object_ids.end(), object.id()) != brim.object_ids.end())
gcode += emit_brim(brim.brim, brim.object_ids);
if (std::find(brim.instances.begin(), brim.instances.end(), object_instance_id) != brim.instances.end())
gcode += emit_brim(brim.brim, brim.instances);
return gcode;
}
const auto brim_it = print.m_brimMap.find(object.id());
if (brim_it == print.m_brimMap.end())
return {};
return emit_brim(brim_it->second, { object.id() });
return {};
}
// Bedslinger model. The heavier the bed load, the lower the achievable Y acceleration for a given
@@ -6096,8 +6108,8 @@ LayerResult GCode::process_layer(
const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id];
const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id];
if (print_wipe_extrusions == (is_anything_overridden ? 1 : 0)) {
gcode += generate_object_skirt_group(print, instance_to_print.print_object, layer_tools, layer, extruder_id);
gcode += generate_object_brim(print, instance_to_print.print_object, first_layer);
gcode += generate_object_skirt_group(print, instance_to_print.print_object, instance_to_print.instance_id, layer_tools, layer, extruder_id);
gcode += generate_object_brim(print, instance_to_print.print_object, instance_to_print.instance_id, first_layer);
}
// To control print speed of the 1st object layer printed over raft interface.
@@ -6152,18 +6164,6 @@ LayerResult GCode::process_layer(
m_layer = layers[instance_to_print.layer_id].support_layer;
m_object_layer_over_raft = false;
//BBS: print supports' brims first
if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && !print_wipe_extrusions) {
this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp();
for (const ExtrusionEntity* ee : print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities) {
gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed));
}
m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point.
m_avoid_crossing_perimeters.disable_once();
this->m_objSupportsWithBrim.erase(instance_to_print.print_object.id());
}
// When starting a new object, use the external motion planner for the first travel move.
const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
@@ -7549,7 +7549,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// If adaptive PA is enabled, by default evaluate PA on all extrusion moves
bool is_pa_calib = m_curr_print->calib_mode() == CalibMode::Calib_PA_Line ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Pattern ||
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
m_curr_print->calib_mode() == CalibMode::Calib_PA_Tower;
bool evaluate_adaptive_pa = false;
bool role_change = (m_last_extrusion_role != path.role());
if (!is_pa_calib && FILAMENT_CONFIG(adaptive_pressure_advance) && FILAMENT_CONFIG(enable_pressure_advance)) {
@@ -9104,7 +9104,7 @@ std::string GCode::set_object_info(Print *print) {
std::ostringstream gcode;
size_t object_id = 0;
// Orca: check if we are in pa calib mode
if (print->calib_mode() == CalibMode::Calib_PA_Line || print->calib_mode() == CalibMode::Calib_PA_Pattern) {
if (print->calib_mode() == CalibMode::Calib_PA_Pattern) {
BoundingBoxf bbox_bed(print->config().printable_area.values);
bbox_bed.offset(-25.0);
Polygon polygon_bed;
@@ -9115,6 +9115,8 @@ std::string GCode::set_object_info(Print *print) {
gcode << "EXCLUDE_OBJECT_DEFINE NAME="
<< "Orca-PA-Calibration-Test"
<< " CENTER=" << 0 << "," << 0 << " POLYGON=" << polygon_to_string(polygon_bed, print, true) << "\n";
} else if (print->calib_mode() == CalibMode::Calib_PA_Line) {
// PA_Line has only one object, no EXCLUDE_OBJECT_DEFINE needed
} else {
size_t unique_id = 0;
for (PrintObject* object : print->objects()) {
+3 -2
View File
@@ -359,11 +359,13 @@ private:
std::vector<coordf_t> &skirt_done);
std::string generate_object_skirt_group(const Print &print,
const PrintObject &object,
size_t instance_id,
const LayerTools &layer_tools,
const Layer& layer,
unsigned int extruder_id);
std::string generate_object_brim(const Print &print,
const PrintObject &object,
size_t instance_id,
bool first_layer);
LayerResult process_layer(
@@ -533,8 +535,7 @@ private:
// BBS
LiftType to_lift_type(ZHopType z_hop_types);
std::set<ObjectID> m_objsWithBrim; // indicates the objs with brim
std::set<ObjectID> m_objSupportsWithBrim; // indicates the objs' supports with brim
std::set<ObjectInstanceID> m_objsWithBrim; // indicates the object instances with brim
// Cache for custom seam enforcers/blockers for each layer.
SeamPlacer m_seam_placer;
-379
View File
@@ -1,379 +0,0 @@
#include "PostProcessor.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/format.hpp"
#include "libslic3r/I18N.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/cstdlib.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/fstream.hpp>
// BBS
#include <iostream>
#include <fstream>
#ifdef WIN32
// The standard Windows includes.
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <shellapi.h>
// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
// This routine appends the given argument to a command line such that CommandLineToArgvW will return the argument string unchanged.
// Arguments in a command line should be separated by spaces; this function does not add these spaces.
// Argument - Supplies the argument to encode.
// CommandLine - Supplies the command line to which we append the encoded argument string.
static void quote_argv_winapi(const std::wstring &argument, std::wstring &commmand_line_out)
{
// Don't quote unless we actually need to do so --- hopefully avoid problems if programs won't parse quotes properly.
if (argument.empty() == false && argument.find_first_of(L" \t\n\v\"") == argument.npos)
commmand_line_out.append(argument);
else {
commmand_line_out.push_back(L'"');
for (auto it = argument.begin(); ; ++ it) {
unsigned number_backslashes = 0;
while (it != argument.end() && *it == L'\\') {
++ it;
++ number_backslashes;
}
if (it == argument.end()) {
// Escape all backslashes, but let the terminating double quotation mark we add below be interpreted as a metacharacter.
commmand_line_out.append(number_backslashes * 2, L'\\');
break;
} else if (*it == L'"') {
// Escape all backslashes and the following double quotation mark.
commmand_line_out.append(number_backslashes * 2 + 1, L'\\');
commmand_line_out.push_back(*it);
} else {
// Backslashes aren't special here.
commmand_line_out.append(number_backslashes, L'\\');
commmand_line_out.push_back(*it);
}
}
commmand_line_out.push_back(L'"');
}
}
static DWORD execute_process_winapi(const std::wstring &command_line)
{
// Extract the current environment to be passed to the child process.
std::wstring envstr;
{
wchar_t *env = GetEnvironmentStrings();
assert(env != nullptr);
const wchar_t* var = env;
size_t totallen = 0;
size_t len;
while ((len = wcslen(var)) > 0) {
totallen += len + 1;
var += len + 1;
}
envstr = std::wstring(env, totallen);
FreeEnvironmentStrings(env);
}
STARTUPINFOW startup_info;
memset(&startup_info, 0, sizeof(startup_info));
startup_info.cb = sizeof(STARTUPINFO);
#if 0
startup_info.dwFlags = STARTF_USESHOWWINDOW;
startup_info.wShowWindow = SW_HIDE;
#endif
PROCESS_INFORMATION process_info;
if (! ::CreateProcessW(
nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */,
CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info))
throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError())));
::WaitForSingleObject(process_info.hProcess, INFINITE);
ULONG rc = 0;
::GetExitCodeProcess(process_info.hProcess, &rc);
::CloseHandle(process_info.hThread);
::CloseHandle(process_info.hProcess);
return rc;
}
// Run the script. If it is a perl script, run it through the bundled perl interpreter.
// If it is a batch file, run it through the cmd.exe.
// Otherwise run it directly.
static int run_script(const std::string &script, const std::string &gcode, std::string &/*std_err*/)
{
// Unpack the argument list provided by the user.
int nArgs;
LPWSTR *szArglist = CommandLineToArgvW(boost::nowide::widen(script).c_str(), &nArgs);
if (szArglist == nullptr || nArgs <= 0) {
// CommandLineToArgvW failed. Maybe the command line escapment is invalid?
throw Slic3r::RuntimeError(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path.");
}
std::wstring command_line;
std::wstring command = szArglist[0];
if (! boost::filesystem::exists(boost::filesystem::path(command)))
throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
if (boost::iends_with(command, L".pl")) {
// This is a perl script. Run it through the perl interpreter.
// The current process may be slic3r.exe or slic3r-console.exe.
// Find the path of the process:
wchar_t wpath_exe[_MAX_PATH + 1];
::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH);
boost::filesystem::path path_exe(wpath_exe);
boost::filesystem::path path_perl = path_exe.parent_path() / "perl" / "perl.exe";
if (! boost::filesystem::exists(path_perl)) {
LocalFree(szArglist);
throw Slic3r::RuntimeError(std::string("Perl interpreter ") + path_perl.string() + " does not exist.");
}
// Replace it with the current perl interpreter.
quote_argv_winapi(boost::nowide::widen(path_perl.string()), command_line);
command_line += L" ";
} else if (boost::iends_with(command, ".bat")) {
// Run a batch file through the command line interpreter.
command_line = L"cmd.exe /C ";
}
for (int i = 0; i < nArgs; ++ i) {
quote_argv_winapi(szArglist[i], command_line);
command_line += L" ";
}
LocalFree(szArglist);
quote_argv_winapi(boost::nowide::widen(gcode), command_line);
return (int)execute_process_winapi(command_line);
}
#else
// POSIX
#include <cstdlib> // getenv()
#include <sstream>
#include <boost/process.hpp>
namespace process = boost::process;
static int run_script(const std::string &script, const std::string &gcode, std::string &std_err)
{
// Try to obtain user's default shell
const char *shell = ::getenv("SHELL");
if (shell == nullptr) { shell = "/bin/sh"; }
// Quote and escape the gcode path argument
std::string command { script };
command.append(" '");
for (char c : gcode) {
if (c == '\'') { command.append("'\\''"); }
else { command.push_back(c); }
}
command.push_back('\'');
BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script, shell: %1%, command: %2%") % shell % command;
process::ipstream istd_err;
process::child child(shell, "-c", command, process::std_err > istd_err);
std_err.clear();
std::string line;
while (child.running() && std::getline(istd_err, line)) {
std_err.append(line);
std_err.push_back('\n');
}
child.wait();
return child.exit_code();
}
#endif
namespace Slic3r {
//! macro used to mark string used at localization,
//! return same string
#define L(s) (s)
#define _(s) Slic3r::I18N::translate(s)
// BBS
void gcode_add_line_number(const std::string& path, const DynamicPrintConfig& config)
{
const ConfigOptionBool* opt = config.opt<ConfigOptionBool>("gcode_add_line_number");
if (!opt->getBool())
return;
auto gcode_file = boost::filesystem::path(path);
if (!boost::filesystem::exists(gcode_file))
return;
std::fstream fs;
std::string new_gcode;
fs.open(gcode_file.c_str(), std::fstream::in | std::fstream::out);
size_t line_number = 1;
std::string gcode_line;
while (std::getline(fs, gcode_line)) {
char num_str[128];
memset(num_str, 0, sizeof(num_str));
snprintf(num_str, sizeof(num_str), "%zd", line_number);
new_gcode += std::string("N") + num_str + " " + gcode_line + "\n";
line_number++;
}
fs.clear();
fs.seekp(0, std::ios_base::beg);
fs.write(new_gcode.c_str(), new_gcode.length());
fs.close();
}
// Run post processing script / scripts if defined.
// Returns true if a post-processing script was executed.
// Returns false if no post-processing script was defined.
// Throws an exception on error.
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
// For a "File" target, a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
// In that case the caller is responsible to delete the temp file created.
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
// The post-processing script may change the output_name.
bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config)
{
const auto *post_process = config.opt<ConfigOptionStrings>("post_process");
if (// likely running in SLA mode
post_process == nullptr ||
// no post-processing script
post_process->values.empty())
return false;
std::string path;
if (make_copy) {
// Don't run the post-processing script on the input file, it will be memory mapped by the G-code viewer.
// Make a copy.
path = src_path + ".pp";
// First delete an old file if it exists.
try {
if (boost::filesystem::exists(path))
boost::filesystem::remove(path);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting an old temporary file %1% before running a post-processing script: %2%", path, err.what());
}
// Second make a copy.
std::string error_message;
if (copy_file(src_path, path, error_message, false) != SUCCESS)
throw Slic3r::RuntimeError(Slic3r::format("Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%", src_path, error_message));
} else {
// Don't make a copy of the G-code before running the post-processing script.
path = src_path;
}
auto delete_copy = [&path, &src_path, make_copy]() {
if (make_copy)
try {
if (boost::filesystem::exists(path))
boost::filesystem::remove(path);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a temporary copy %1% of a G-code file %2% : %3%", path, src_path, err.what());
}
};
auto gcode_file = boost::filesystem::path(path);
if (! boost::filesystem::exists(gcode_file))
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
// Store print configuration into environment variables.
config.setenv_();
// Let the post-processing script know the target host ("File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...)
boost::nowide::setenv("SLIC3R_PP_HOST", host.c_str(), 1);
// Let the post-processing script know the final file name. For "File" host, it is a full path of the target file name and its location, for example pointing to an SD card.
// For "PrusaLink" or "OctoPrint", it is a file name optionally with a directory on the target host.
boost::nowide::setenv("SLIC3R_PP_OUTPUT_NAME", output_name.c_str(), 1);
// Path to an optional file that the post-processing script may create and populate it with a single line containing the output_name replacement.
std::string path_output_name = path + ".output_name";
auto remove_output_name_file = [&path_output_name, &src_path]() {
try {
if (boost::filesystem::exists(path_output_name))
boost::filesystem::remove(path_output_name);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a file %1% carrying the final name / path of a G-code file %2%: %3%", path_output_name, src_path, err.what());
}
};
// Remove possible stalled path_output_name of the previous run.
remove_output_name_file();
try {
for (const std::string &scripts : post_process->values) {
std::vector<std::string> lines;
boost::split(lines, scripts, boost::is_any_of("\r\n"));
for (std::string script : lines) {
// Ignore empty post processing script lines.
boost::trim(script);
if (script.empty())
continue;
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
std::string std_err;
const int result = run_script(script, gcode_file.string(), std_err);
if (result != 0) {
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
BOOST_LOG_TRIVIAL(error) << msg;
delete_copy();
throw Slic3r::RuntimeError(msg);
}
if (! boost::filesystem::exists(gcode_file)) {
const std::string msg = (boost::format(_(L(
"Post-processing script %1% failed.\n\n"
"The post-processing script is expected to change the G-code file %2% in place, but the G-code file was deleted and likely saved under a new name.\n"
"Please adjust the post-processing script to change the G-code in place and consult the manual on how to optionally rename the post-processed G-code file.\n")))
% script % path).str();
BOOST_LOG_TRIVIAL(error) << msg;
throw Slic3r::RuntimeError(msg);
}
}
}
if (boost::filesystem::exists(path_output_name)) {
try {
// Read a single line from path_output_name, which should contain the new output name of the post-processed G-code.
boost::nowide::fstream f;
f.open(path_output_name, std::ios::in);
std::string new_output_name;
std::getline(f, new_output_name);
f.close();
if (host == "File") {
namespace fs = boost::filesystem;
fs::path op(new_output_name);
if (op.is_relative() && op.has_filename() && op.parent_path().empty()) {
// Is this just a filename? Make it an absolute path.
auto outpath = fs::path(output_name).parent_path();
outpath /= op.string();
new_output_name = outpath.string();
}
else {
if (! op.is_absolute() || ! op.has_filename())
throw Slic3r::RuntimeError("Unable to parse desired new path from output name file");
}
if (! fs::exists(fs::path(new_output_name).parent_path()))
throw Slic3r::RuntimeError(Slic3r::format("Output directory does not exist: %1%",
fs::path(new_output_name).parent_path().string()));
}
BOOST_LOG_TRIVIAL(trace) << "Post-processing script changed the file name from " << output_name << " to " << new_output_name;
output_name = new_output_name;
} catch (const std::exception &err) {
throw Slic3r::RuntimeError(Slic3r::format("run_post_process_scripts: Failed reading a file %1% "
"carrying the final name / path of a G-code file: %2%",
path_output_name, err.what()));
}
remove_output_name_file();
}
} catch (...) {
remove_output_name_file();
delete_copy();
throw;
}
src_path = std::move(path);
return true;
}
} // namespace Slic3r
-34
View File
@@ -1,34 +0,0 @@
#ifndef slic3r_GCode_PostProcessor_hpp_
#define slic3r_GCode_PostProcessor_hpp_
#include <string>
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
namespace Slic3r {
// Run post processing script / scripts if defined.
// Returns true if a post-processing script was executed.
// Returns false if no post-processing script was defined.
// Throws an exception on error.
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
// If make_copy, then a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
// In that case the caller is responsible to delete the temp file created.
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
// The post-processing script may change the output_name.
extern bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config);
inline bool run_post_process_scripts(std::string &src_path, const DynamicPrintConfig &config)
{
std::string src_path_name = src_path;
return run_post_process_scripts(src_path, false, "File", src_path_name, config);
}
// BBS
extern void gcode_add_line_number(const std::string &path, const DynamicPrintConfig &config);
} // namespace Slic3r
#endif /* slic3r_GCode_PostProcessor_hpp_ */
+13
View File
@@ -3,6 +3,7 @@
#include <cereal/access.hpp>
#include <cereal/types/base_class.hpp>
#include <cstddef>
namespace Slic3r {
@@ -41,6 +42,18 @@ private:
template<class Archive> void serialize(Archive &ar) { ar(id); }
};
struct ObjectInstanceID {
ObjectID object_id;
size_t instance_id { size_t(-1) };
bool operator==(const ObjectInstanceID& rhs) const { return object_id == rhs.object_id && instance_id == rhs.instance_id; }
bool operator!=(const ObjectInstanceID& rhs) const { return !(*this == rhs); }
bool operator<(const ObjectInstanceID& rhs) const
{
return object_id < rhs.object_id || (object_id == rhs.object_id && instance_id < rhs.instance_id);
}
};
// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID
// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject).
// Also base for Print, PrintObject, SLAPrint, SLAPrintObject to provide a unique ID for matching Model / ModelObject
+26
View File
@@ -105,6 +105,32 @@ PlatformFlavor platform_flavor()
return s_platform_flavor;
}
std::string platform_os_type()
{
#if defined(_WIN32)
return "win";
#elif defined(__APPLE__)
return "macos";
#elif defined(__linux__) || defined(__LINUX__)
return "linux";
#else
return "unknown";
#endif
}
std::string platform_architecture()
{
#if defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64)
return "arm64";
#elif defined(__x86_64__) || defined(__x86_64) || defined(__amd64__) || defined(__amd64) || defined(_M_X64) || defined(_M_AMD64)
return "x86_64";
#elif defined(__i386__) || defined(__i386) || defined(i386) || defined(_M_IX86)
return "i386";
#else
return "unknown";
#endif
}
std::string platform_to_string(Platform platform)
+2
View File
@@ -36,6 +36,8 @@ void detect_platform();
Platform platform();
PlatformFlavor platform_flavor();
std::string platform_os_type();
std::string platform_architecture();
std::string platform_to_string(Platform platform);
std::string platform_flavor_to_string(PlatformFlavor pf);
+19 -3
View File
@@ -1201,6 +1201,9 @@ static std::vector<std::string> s_Preset_print_options{
"min_feature_size",
"min_bead_width",
"post_process",
"slicing_pipeline_plugin",
"plugins",
"plugin_config_overrides",
"process_change_extrusion_role_gcode",
"min_length_factor",
"wall_maximum_resolution",
@@ -1375,6 +1378,7 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
"filament_preheat_temperature_delta", "filament_retract_length_nc",
"filament_change_length_nc", "filament_prime_volume_nc",
"long_retractions_when_ec", "retraction_distances_when_ec",
"plugin_config_overrides",
//ams chamber
"filament_dev_ams_drying_ams_limitations", "filament_dev_ams_drying_temperature", "filament_dev_ams_drying_time", "filament_dev_ams_drying_heat_distortion_temperature",
"filament_dev_chamber_drying_bed_temperature", "filament_dev_chamber_drying_time",
@@ -1425,7 +1429,8 @@ static std::vector<std::string> s_Preset_printer_options {
"machine_hotend_change_time", "machine_prepare_compensation_time",
// Fast-purge printer flag + device/firmware-facing per-variant extruder-change
// deretraction speed (unconsumed by the slicer; carried by H2D/A2L/X2D/P2S machine profiles).
"support_fast_purge_mode", "deretract_speed_extruder_change"
"support_fast_purge_mode", "deretract_speed_extruder_change",
"plugin_config_overrides"
};
static std::vector<std::string> s_Preset_sla_print_options {
@@ -3453,7 +3458,18 @@ void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys&
for (int i = 0; i < int(opt_cur->values.size()); i++)
{
int init_id = i <= opt_init_max_id ? i : 0;
const bool is_new_index = i > opt_init_max_id;
int init_id = is_new_index ? 0 : i;
if (is_new_index) {
// Orca: intentional divergence from upstream. Any new vector index (at or
// beyond the reference vector's length) is flagged dirty unconditionally --
// independent of its value and nil-state -- so preset dirty-detection notices
// per-extruder/filament entries added by growth (e.g. extruder count). This
// applies to every vector option type routed through deep_diff().
// Covered by tests/libslic3r/test_preset_diff.cpp.
vec.emplace_back(opt_key + "#" + std::to_string(i));
continue;
}
if (opt_cur->values[i] != opt_init->values[init_id]) {
if (opt_cur->nullable()) {
if (opt_cur->is_nil(i)) {
@@ -3488,7 +3504,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
{
//BBS: add bed_exclude_area
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area") {
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area" || opt_key == "slicing_pipeline_plugin") {
// Scalar variable, or a vector variable, which is independent from number of extruders,
// thus the vector is presented to the user as a single input.
diff.emplace_back(opt_key);
+175 -90
View File
@@ -51,6 +51,8 @@ using namespace nlohmann;
namespace Slic3r {
Print::SlicingPipelineHookFn Print::s_slicing_pipeline_hook_fn = nullptr;
template class PrintState<PrintStep, psCount>;
template class PrintState<PrintObjectStep, posCount>;
@@ -124,6 +126,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"printing_by_object_gcode",
"filament_end_gcode",
"post_process",
// "plugins" is the derived manifest backing the plugin-picker options; on its own it only
// affects G-code export. The specific option (e.g. slicing_pipeline_plugin) drives any re-slice.
"plugins",
"extruder_clearance_height_to_rod",
"extruder_clearance_height_to_lid",
"extruder_clearance_radius",
@@ -279,7 +284,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "wipe_tower_rotation_angle") {
steps.emplace_back(psSkirtBrim);
} else if (
opt_key == "initial_layer_print_height"
opt_key == "slicing_pipeline_plugin"
|| opt_key == "initial_layer_print_height"
|| opt_key == "nozzle_diameter"
|| opt_key == "filament_shrink"
|| opt_key == "filament_shrinkage_compensation_z"
@@ -2207,6 +2213,11 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
if (time_cost_with_cache)
*time_cost_with_cache = 0;
{
const auto* sp = this->config().option<ConfigOptionStrings>("slicing_pipeline_plugin");
m_pipeline_plugin_active = s_slicing_pipeline_hook_fn && sp && !sp->values.empty();
}
name_tbb_thread_pool_threads_set_locale();
//compute the PrintObject with the same geometries
@@ -2316,20 +2327,47 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": total object counts %1% in current print, need to slice %2%")%m_objects.size()%need_slicing_objects.size();
BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info();
if (!use_cache) {
// Fire the SlicingPipeline hook for `obj` iff it just (re)computed `pstep` this pass.
auto hook_after = [this](PrintObject* obj, bool was_done, PrintObjectStep pstep, SlicingPipelineStepPlugin sstep) {
if (m_pipeline_plugin_active && !was_done && obj->is_step_done(pstep))
run_pipeline_hook(sstep, obj);
};
// SlicingPipeline: dedicated slice loop so the Slice boundary is hookable before perimeters.
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
obj->make_perimeters();
}
else {
if (obj->set_started(posSlice))
obj->set_done(posSlice);
if (obj->set_started(posPerimeters))
obj->set_done(posPerimeters);
const bool was_done = obj->is_step_done(posSlice);
obj->slice();
hook_after(obj, was_done, posSlice, SlicingPipelineStepPlugin::posSlice);
// re-snapshot each layer's raw_slices AFTER the Slice hook ran, so the
// plugin's mutation becomes the untyped baseline. Without this, a later
// perimeter-only re-run (make_perimeters -> restore_untyped_slices) reverts
// slices to the PRE-hook geometry while posSlice stays cached (the hook does
// not re-fire), silently un-applying the mutation; raw_slices consumers
// (sharp-tail support, ToolOrdering) also read this backup directly. Gated on
// an active plugin AND a genuine (re)slice, so the inactive path is untouched
// and re-backing-up an unmutated layer is a harmless identical copy.
if (m_pipeline_plugin_active && !was_done && obj->is_step_done(posSlice))
for (Layer *layer : obj->layers())
layer->backup_untyped_slices();
} else {
if (obj->set_started(posSlice)) obj->set_done(posSlice); // shared/duplicate — no hook
}
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posPerimeters);
obj->make_perimeters(); // slice() inside is a no-op: posSlice already DONE
hook_after(obj, was_done, posPerimeters, SlicingPipelineStepPlugin::posPerimeters);
} else {
if (obj->set_started(posPerimeters)) obj->set_done(posPerimeters);
}
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posEstimateCurledExtrusions);
obj->estimate_curled_extrusions();
hook_after(obj, was_done, posEstimateCurledExtrusions, SlicingPipelineStepPlugin::posEstimateCurledExtrusions);
}
else {
if (obj->set_started(posEstimateCurledExtrusions))
@@ -2338,7 +2376,17 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
// split prepare_infill (fill-surface prep) from infill (make_fills) so a
// plugin can mutate fill surfaces at the PrepareInfill seam and have make_fills
// consume them (unlike the Infill seam, which fires after the fills are already
// built). infill() re-invokes prepare_infill() as a no-op once posPrepareInfill
// is DONE, so this is a mechanical split mirroring the slice/perimeters loop.
const bool prepare_was_done = obj->is_step_done(posPrepareInfill);
obj->prepare_infill();
hook_after(obj, prepare_was_done, posPrepareInfill, SlicingPipelineStepPlugin::posPrepareInfill);
const bool was_done = obj->is_step_done(posInfill);
obj->infill();
hook_after(obj, was_done, posInfill, SlicingPipelineStepPlugin::posInfill);
}
else {
if (obj->set_started(posPrepareInfill))
@@ -2349,7 +2397,9 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
for (PrintObject *obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posIroning);
obj->ironing();
hook_after(obj, was_done, posIroning, SlicingPipelineStepPlugin::posIroning);
}
else {
if (obj->set_started(posIroning))
@@ -2361,13 +2411,22 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
for (PrintObject *obj : m_objects) {
bool need_contouring = need_slicing_objects.count(obj) != 0 && obj->need_z_contouring();
if (need_contouring) {
const bool was_done = obj->is_step_done(posContouring);
obj->contour_z();
hook_after(obj, was_done, posContouring, SlicingPipelineStepPlugin::posContouring);
} else {
if (obj->set_started(posContouring))
obj->set_done(posContouring);
}
}
// SlicingPipeline: support runs in the parallel block below; the hook must fire in a
// sequential loop afterward. Snapshot per-object done-state just before the parallel_for.
std::vector<char> sup_was_done(m_objects.size(), 1);
if (m_pipeline_plugin_active)
for (size_t i = 0; i < m_objects.size(); ++i)
sup_was_done[i] = m_objects[i]->is_step_done(posSupportMaterial) ? 1 : 0;
tbb::parallel_for(tbb::blocked_range<int>(0, int(m_objects.size())),
[this, need_slicing_objects](const tbb::blocked_range<int>& range) {
for (int i = range.begin(); i < range.end(); i++) {
@@ -2383,9 +2442,17 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
);
if (m_pipeline_plugin_active)
for (size_t i = 0; i < m_objects.size(); ++i)
if (need_slicing_objects.count(m_objects[i]) != 0 && !sup_was_done[i]
&& m_objects[i]->is_step_done(posSupportMaterial))
run_pipeline_hook(SlicingPipelineStepPlugin::posSupportMaterial, m_objects[i]);
for (PrintObject* obj : m_objects) {
if (need_slicing_objects.count(obj) != 0) {
const bool was_done = obj->is_step_done(posDetectOverhangsForLift);
obj->detect_overhangs_for_lift();
hook_after(obj, was_done, posDetectOverhangsForLift, SlicingPipelineStepPlugin::posDetectOverhangsForLift);
}
else {
if (obj->set_started(posDetectOverhangsForLift))
@@ -2462,6 +2529,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
this->set_done(psWipeTower);
if (m_pipeline_plugin_active) run_pipeline_hook(SlicingPipelineStepPlugin::psWipeTower, nullptr);
}
if (this->has_wipe_tower()) {
@@ -2478,8 +2546,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
m_skirt_brim_groups.clear();
m_has_shared_per_object_skirt = false;
m_skirt_convex_hull.clear();
m_objectBrimAreas.clear();
m_supportBrimAreas.clear();
m_objectBrimAreasByInstance.clear();
m_first_layer_convex_hull.points.clear();
for (PrintObject *object : m_objects) object->m_skirt.clear();
@@ -2635,14 +2702,16 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
if (!existObject && objectExtruderMap.find(print_object_ID) != objectExtruderMap.end())
objPrintVec.push_back(std::make_pair(print_object_ID, objectExtruderMap.at(print_object_ID)));
}
// BBS: m_brimMap and m_supportBrimMap are used instead of m_brim to generate brim of objs and supports seperately
// Orca: Build both the old object-keyed brim map and the per-instance
// maps used by skirt/brim groups.
m_brimMap.clear();
m_supportBrimMap.clear();
m_brimMapByInstance.clear();
m_first_layer_convex_hull.points.clear();
if (this->has_brim()) {
Polygons islands_area;
make_brim(*this, this->make_try_cancel(), islands_area, m_brimMap,
m_supportBrimMap, objPrintVec, printExtruders, &m_objectBrimAreas, &m_supportBrimAreas);
m_brimMapByInstance, objPrintVec, printExtruders,
&m_objectBrimAreasByInstance);
for (Polygon& poly_ex : islands_area)
poly_ex.douglas_peucker(SCALED_RESOLUTION);
for (Polygon &poly : union_(this->first_layer_islands(), islands_area))
@@ -2664,6 +2733,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
this->finalize_first_layer_convex_hull();
this->set_done(psSkirtBrim);
if (m_pipeline_plugin_active) run_pipeline_hook(SlicingPipelineStepPlugin::psSkirtBrim, nullptr);
if (time_cost_with_cache) {
end_time = (long long)Slic3r::Utils::get_current_time_utc();
@@ -2674,7 +2744,13 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
for (PrintObject *obj : m_objects) {
if (((!use_cache)&&(need_slicing_objects.count(obj) != 0))
|| (use_cache &&(re_slicing_objects.count(obj) != 0))){
const bool was_done = obj->is_step_done(posSimplifyPath);
obj->simplify_extrusion_path();
// Unlike every other seam (all inside the `if (!use_cache)` block above), this loop is
// shared with the use_cache path (re_slicing_objects), so `!use_cache` must be checked
// explicitly here to keep hooks from ever firing on cache-loaded (plugin-final) objects.
if (!use_cache && m_pipeline_plugin_active && !was_done && obj->is_step_done(posSimplifyPath))
run_pipeline_hook(SlicingPipelineStepPlugin::posSimplifyPath, obj);
}
else {
if (obj->set_started(posSimplifyPath))
@@ -2784,7 +2860,8 @@ void Print::_make_skirt()
Polygon hull;
};
// Orca: build one local occupied hull per object from object and support geometry up to skirt height.
// Orca: Build one local occupied hull per object from object/support
// geometry up to skirt height. Instances translate this hull later.
std::vector<ObjectSkirtHull> object_convex_hulls;
for (PrintObject *object : m_objects) {
Points object_points;
@@ -2894,49 +2971,49 @@ void Print::_make_skirt()
object_hull.object->m_skirt.clear();
if (m_config.skirt_type == stCombined || m_config.skirt_type == stPerObject) {
struct SkirtGroupItem {
struct SkirtBrimGroupItem {
Points occupied_points;
ObjectID object_id;
size_t instance_id;
bool emits_skirt;
};
// Orca: group items represent occupied first-layer areas. Object items emit skirts;
// obstacle-only items, such as wipe tower, only force nearby object groups to merge.
std::vector<SkirtGroupItem> group_items;
// Orca: Each object instance can emit skirt/brim. Wipe tower is only an
// obstacle here; it may merge nearby items, but does not emit anything.
std::vector<SkirtBrimGroupItem> group_items;
const coord_t grouping_offset = scale_(m_config.skirt_distance.value + m_config.skirt_loops.value * spacing);
for (const ObjectSkirtHull& object_hull : object_convex_hulls) {
PrintObject* object = object_hull.object;
Points occupied_points;
for (const PrintInstance &instance : object->instances()) {
std::vector<size_t> object_item_indices;
object_item_indices.reserve(object->instances().size());
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
const PrintInstance &instance = object->instances()[instance_idx];
Points copy_points = object_hull.hull.points;
for (Point &pt : copy_points)
pt += instance.shift;
append(occupied_points, copy_points);
if (copy_points.size() < 3)
continue;
object_item_indices.push_back(group_items.size());
group_items.push_back({ std::move(copy_points), object->id(), instance_idx, true });
}
auto append_brim_points = [&occupied_points](const ExPolygons& areas) {
for (const ExPolygon& area : areas)
append(occupied_points, area.contour.points);
};
if (auto it = m_objectBrimAreas.find(object->id()); it != m_objectBrimAreas.end())
append_brim_points(it->second);
if (auto it = m_supportBrimAreas.find(object->id()); it != m_supportBrimAreas.end())
append_brim_points(it->second);
if (occupied_points.size() < 3)
continue;
// Orca: include the object's brim/support-brim footprint before checking skirt collisions.
group_items.push_back({ std::move(occupied_points), object->id(), true });
for (size_t item_idx : object_item_indices) {
const ObjectInstanceID key{ object->id(), group_items[item_idx].instance_id };
if (auto instance_brim_it = m_objectBrimAreasByInstance.find(key); instance_brim_it != m_objectBrimAreasByInstance.end())
for (const ExPolygon& area : instance_brim_it->second)
append(group_items[item_idx].occupied_points, area.contour.points);
}
}
// Orca: the wipe tower contributes occupied area, but does not emit a skirt by itself.
Points wipe_tower_points = this->first_layer_wipe_tower_corners();
if (wipe_tower_points.size() >= 3)
group_items.push_back({ std::move(wipe_tower_points), ObjectID(), false });
group_items.push_back({ std::move(wipe_tower_points), ObjectID(), size_t(-1), false });
std::vector<size_t> parent(group_items.size());
std::iota(parent.begin(), parent.end(), 0);
// Orca: union-find keeps collision merging local without repeatedly rebuilding item lists.
// Orca: Use union-find so touching items can be merged while scanning.
auto find_parent = [&parent](size_t idx) {
while (parent[idx] != idx) {
parent[idx] = parent[parent[idx]];
@@ -2951,24 +3028,24 @@ void Print::_make_skirt()
parent[b] = a;
};
// Orca: combined skirt is the same grouping model with all items forced into one group.
// Orca: Combined skirt starts with all items in the same group.
if (m_config.skirt_type == stCombined && !group_items.empty())
for (size_t i = 1; i < group_items.size(); ++i)
unite(0, i);
auto build_grouped_points = [&]() {
struct GroupData {
Points points;
std::vector<ObjectID> object_ids;
bool emits_skirt = false;
auto build_skirt_brim_groups = [&]() {
struct SkirtBrimGroupData {
Points points;
std::vector<ObjectInstanceID> instances;
bool emits_skirt = false;
};
std::map<size_t, GroupData> grouped;
std::map<size_t, SkirtBrimGroupData> grouped;
for (size_t i = 0; i < group_items.size(); ++i) {
GroupData& group = grouped[find_parent(i)];
SkirtBrimGroupData& group = grouped[find_parent(i)];
append(group.points, group_items[i].occupied_points);
if (group_items[i].object_id.valid())
group.object_ids.push_back(group_items[i].object_id);
group.instances.push_back({ group_items[i].object_id, group_items[i].instance_id });
group.emits_skirt = group.emits_skirt || group_items[i].emits_skirt;
}
return grouped;
@@ -2977,16 +3054,18 @@ void Print::_make_skirt()
bool groups_changed = m_config.skirt_type == stPerObject;
while (groups_changed) {
groups_changed = false;
auto grouped_points = build_grouped_points();
auto grouped_points = build_skirt_brim_groups();
std::vector<std::pair<size_t, Polygon>> group_envelopes;
for (const auto& [root, group] : grouped_points) {
if (group.points.size() < 3)
continue;
// Orca: emitting groups are expanded to their final skirt reach; obstacle groups are not.
// Orca: Only skirt-emitting groups are expanded by skirt distance;
// obstacle-only groups stay at their occupied outline.
Polygon envelope = Geometry::convex_hull(group.points);
if (group.emits_skirt) {
// Orca: merge groups when a skirt envelope intersects another group or obstacle.
// Orca: If the expanded skirt outline touches another group
// or obstacle, merge them and run the pass again.
Polygons envelopes = offset(envelope, grouping_offset, ClipperLib::jtRound, float(scale_(0.1)));
if (envelopes.empty())
continue;
@@ -3007,28 +3086,23 @@ void Print::_make_skirt()
}
}
auto make_group_brims = [this](const std::vector<ObjectID>& group_object_ids) {
auto make_brims_for_skirt_brim_group = [this](const std::vector<ObjectInstanceID>& group_instances) {
std::vector<SkirtBrimGroup::Brim> brims;
std::vector<ObjectID> brim_object_ids;
for (ObjectID object_id : group_object_ids) {
const auto brim_it = m_brimMap.find(object_id);
if (brim_it != m_brimMap.end() && !brim_it->second.empty())
brim_object_ids.push_back(object_id);
std::vector<ObjectInstanceID> brim_instances;
for (const ObjectInstanceID& instance : group_instances) {
const auto brim_it = m_brimMapByInstance.find(instance);
if (brim_it != m_brimMapByInstance.end() && !brim_it->second.empty())
brim_instances.push_back(instance);
}
const bool global_combined_brim = m_config.combine_brims && m_config.skirt_type != stPerObject && m_brimMap.size() == 1;
auto brim_owner_ids = [&group_object_ids, global_combined_brim](const std::vector<ObjectID>& object_ids) {
return global_combined_brim ? group_object_ids : object_ids;
};
const bool combine_group_brims = m_config.combine_brims && brim_object_ids.size() > 1;
const bool combine_group_brims = m_config.combine_brims && brim_instances.size() > 1;
if (!combine_group_brims) {
for (ObjectID object_id : brim_object_ids)
brims.push_back({ m_brimMap.at(object_id), brim_owner_ids({ object_id }) });
for (const ObjectInstanceID& instance : brim_instances)
brims.push_back({ m_brimMapByInstance.at(instance), { instance } });
return brims;
}
std::vector<size_t> brim_parent(brim_object_ids.size());
std::vector<size_t> brim_parent(brim_instances.size());
std::iota(brim_parent.begin(), brim_parent.end(), 0);
auto find_brim_parent = [&brim_parent](size_t idx) {
while (brim_parent[idx] != idx) {
@@ -3045,61 +3119,64 @@ void Print::_make_skirt()
};
const coord_t brim_contact_distance = coord_t(brim_flow().scaled_spacing() * 2.);
for (size_t i = 0; i < brim_object_ids.size(); ++i) {
const auto area_i = m_objectBrimAreas.find(brim_object_ids[i]);
if (area_i == m_objectBrimAreas.end())
for (size_t i = 0; i < brim_instances.size(); ++i) {
const auto area_i = m_objectBrimAreasByInstance.find(brim_instances[i]);
if (area_i == m_objectBrimAreasByInstance.end())
continue;
for (size_t j = i + 1; j < brim_object_ids.size(); ++j) {
const auto area_j = m_objectBrimAreas.find(brim_object_ids[j]);
if (area_j != m_objectBrimAreas.end() &&
for (size_t j = i + 1; j < brim_instances.size(); ++j) {
const auto area_j = m_objectBrimAreasByInstance.find(brim_instances[j]);
if (area_j != m_objectBrimAreasByInstance.end() &&
!intersection_ex(offset_ex(area_i->second, brim_contact_distance, jtRound, SCALED_RESOLUTION), area_j->second).empty())
unite_brims(i, j);
}
}
std::map<size_t, std::vector<ObjectID>> combined_brim_ids;
for (size_t i = 0; i < brim_object_ids.size(); ++i)
combined_brim_ids[find_brim_parent(i)].push_back(brim_object_ids[i]);
std::map<size_t, std::vector<ObjectInstanceID>> combined_brim_ids;
for (size_t i = 0; i < brim_instances.size(); ++i)
combined_brim_ids[find_brim_parent(i)].push_back(brim_instances[i]);
for (const auto& [_, object_ids] : combined_brim_ids) {
if (object_ids.size() == 1) {
brims.push_back({ m_brimMap.at(object_ids.front()), brim_owner_ids(object_ids) });
for (const auto& [_, instances] : combined_brim_ids) {
if (instances.size() == 1) {
const ObjectInstanceID& instance = instances.front();
brims.push_back({ m_brimMapByInstance.at(instance), { instance } });
continue;
}
ExPolygons combined_area;
for (ObjectID object_id : object_ids)
expolygons_append(combined_area, m_objectBrimAreas.at(object_id));
for (const ObjectInstanceID& instance : instances)
expolygons_append(combined_area, m_objectBrimAreasByInstance.at(instance));
combined_area = union_ex(combined_area);
const float scaled_resolution = float(scaled(m_config.resolution.value));
const float brim_cleanup_delta = std::max(scaled_resolution, float(SCALED_EPSILON));
combined_area = offset2_ex(combined_area, brim_cleanup_delta, -brim_cleanup_delta, jtRound, scaled_resolution);
Polygons islands_area;
brims.push_back({ makeBrimInfillFromPlateCoordinates(combined_area, *this, islands_area), object_ids });
brims.push_back({ makeBrimInfillFromPlateCoordinates(combined_area, *this, islands_area), instances });
}
return brims;
};
auto grouped_points = build_grouped_points();
auto grouped_points = build_skirt_brim_groups();
for (auto& [_, group] : grouped_points) {
if (!group.emits_skirt || group.points.size() < 3)
continue;
if (generate_skirt && m_config.skirt_type == stPerObject && group.object_ids.size() > 1)
if (generate_skirt && m_config.skirt_type == stPerObject && group.instances.size() > 1)
m_has_shared_per_object_skirt = true;
// Orca: after merging, use the occupied outline directly; do not add skirt distance twice.
// Orca: Group points already include the occupied outline, so don't
// add skirt distance here again.
ExtrusionEntityCollection group_skirt;
if (generate_skirt)
append_skirt_loops_for_hull(Geometry::convex_hull(group.points), group_skirt, true);
std::vector<SkirtBrimGroup::Brim> group_brims = make_group_brims(group.object_ids);
std::vector<SkirtBrimGroup::Brim> group_brims = make_brims_for_skirt_brim_group(group.instances);
if (!group_skirt.empty()) {
group_skirt.reverse();
// Orca: keep m_skirt as a flattened compatibility mirror for preview/extents.
// Orca: Keep m_skirt filled for code that still reads a flat
// skirt collection.
m_skirt.append(group_skirt.entities);
}
if (!group_skirt.empty() || !group_brims.empty())
m_skirt_brim_groups.push_back({ std::move(group_skirt), std::move(group.object_ids), std::move(group_brims) });
m_skirt_brim_groups.push_back({ std::move(group_skirt), std::move(group.instances), std::move(group_brims) });
}
}
}
@@ -4049,6 +4126,7 @@ void Print::_make_wipe_tower()
// Unload the current filament over the purge tower.
coordf_t layer_height = m_objects.front()->config().layer_height.value;
bool generate_final_purge = true;
if (m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions > 0) {
// The wipe tower goes up to the last layer of the print.
if (wipe_tower.layer_finished()) {
@@ -4060,11 +4138,14 @@ void Print::_make_wipe_tower()
// There is yet enough space at this layer of the wipe tower for the final purge.
}
} else {
// The wipe tower does not reach the last print layer, perform the pruge at the last print layer.
// The wipe tower does not reach the last print layer.
// Skip final purge to avoid generating purge lines in mid-air.
assert(m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions == 0);
wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true);
generate_final_purge = false;
}
m_wipe_tower_data.final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int) (-1)));
m_wipe_tower_data.final_purge = generate_final_purge
? Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int)(-1)))
: Slic3r::make_unique<WipeTower::ToolChangeResult>();
m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
@@ -4160,6 +4241,7 @@ void Print::_make_wipe_tower()
// Unload the current filament over the purge tower.
coordf_t layer_height = m_objects.front()->config().layer_height.value;
bool generate_final_purge = true;
if (m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions > 0) {
// The wipe tower goes up to the last layer of the print.
if (wipe_tower.layer_finished()) {
@@ -4171,11 +4253,14 @@ void Print::_make_wipe_tower()
// There is yet enough space at this layer of the wipe tower for the final purge.
}
} else {
// The wipe tower does not reach the last print layer, perform the pruge at the last print layer.
// The wipe tower does not reach the last print layer.
// Skip final purge to avoid generating purge lines in mid-air.
assert(m_wipe_tower_data.tool_ordering.back().wipe_tower_partitions == 0);
wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true);
generate_final_purge = false;
}
m_wipe_tower_data.final_purge = Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int) (-1)));
m_wipe_tower_data.final_purge = generate_final_purge
? Slic3r::make_unique<WipeTower::ToolChangeResult>(wipe_tower.tool_change((unsigned int)(-1)))
: Slic3r::make_unique<WipeTower::ToolChangeResult>();
m_wipe_tower_data.used_filament = wipe_tower.get_used_filament();
m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges();
+29 -8
View File
@@ -17,6 +17,7 @@
#include "GCode/ThumbnailData.hpp"
#include "GCode/GCodeProcessor.hpp"
#include "MultiMaterialSegmentation.hpp"
#include "ObjectID.hpp"
#include "libslic3r.h"
#include <Eigen/Geometry>
@@ -101,6 +102,14 @@ enum PrintObjectStep {
posCount,
};
enum class SlicingPipelineStepPlugin {
posSlice, posPerimeters, posEstimateCurledExtrusions, posPrepareInfill, posInfill, posIroning, posContouring,
posSupportMaterial, posDetectOverhangsForLift, posSimplifyPath, psWipeTower, psSkirtBrim,
// Fires from the GUI G-code export/post-process seam (PostProcessor.cpp), NOT from Print::process().
// At this step the plugin edits the exported G-code file in place; see SlicingPipelinePluginCapability for the full contract.
psGCodePostProcess
};
// A PrintRegion object represents a group of volumes to print
// sharing the same config (including the same assigned extruder(s))
class PrintRegion
@@ -894,6 +903,11 @@ private: // Prevents erroneous use by other classes.
typedef std::pair<PrintObject *, bool> PrintObjectInfo;
public:
using SlicingPipelineHookFn = std::function<void(Print&, const PrintObject*, SlicingPipelineStepPlugin)>;
// Cross-layer injection (mirrors ConfigBase::set_resolve_capability_fn): the GUI/plugin
// layer registers a dispatcher; libslic3r stays free of any plugin/Python dependency.
static void set_slicing_pipeline_hook_fn(SlicingPipelineHookFn fn) { s_slicing_pipeline_hook_fn = std::move(fn); }
Print() = default;
virtual ~Print() { this->clear(); }
@@ -961,7 +975,7 @@ public:
[object_id](const PrintObject *obj) { return obj->id() == object_id; });
return (it == m_objects.end()) ? nullptr : *it;
}
//BBS: Function to get m_brimMap;
// Orca: Old callers still expect object-keyed brim paths.
std::map<ObjectID, ExtrusionEntityCollection>&
get_brimMap() { return m_brimMap; }
@@ -976,11 +990,11 @@ public:
struct SkirtBrimGroup {
struct Brim {
ExtrusionEntityCollection brim;
std::vector<ObjectID> object_ids;
std::vector<ObjectInstanceID> instances;
};
ExtrusionEntityCollection skirt;
std::vector<ObjectID> object_ids;
std::vector<ObjectInstanceID> instances;
// Brims stay separate unless Combine brims merges colliding brims inside this group.
std::vector<Brim> brims;
};
@@ -1263,6 +1277,13 @@ private:
// Islands of objects and their supports extruded at the 1st layer.
Polygons first_layer_islands() const;
static SlicingPipelineHookFn s_slicing_pipeline_hook_fn;
bool m_pipeline_plugin_active { false };
void run_pipeline_hook(SlicingPipelineStepPlugin step, const PrintObject* object) {
if (m_pipeline_plugin_active && s_slicing_pipeline_hook_fn)
s_slicing_pipeline_hook_fn(*this, object, step);
}
PrintConfig m_config;
PrintObjectConfig m_default_object_config;
PrintRegionConfig m_default_region_config;
@@ -1276,12 +1297,12 @@ private:
ExtrusionEntityCollection m_skirt;
std::vector<SkirtBrimGroup> m_skirt_brim_groups;
bool m_has_shared_per_object_skirt { false };
// BBS: collecting extrusion paths to build brim by objs
// Orca: Object-keyed brim paths kept for existing code.
std::map<ObjectID, ExtrusionEntityCollection> m_brimMap;
std::map<ObjectID, ExtrusionEntityCollection> m_supportBrimMap;
// Orca: cached occupied brim footprints used when grouping per-object skirts.
std::map<ObjectID, ExPolygons> m_objectBrimAreas;
std::map<ObjectID, ExPolygons> m_supportBrimAreas;
// Orca: Actual brim paths keyed by object instance.
std::map<ObjectInstanceID, ExtrusionEntityCollection> m_brimMapByInstance;
// Orca: Translated brim areas keyed by instance, used to find touching brims.
std::map<ObjectInstanceID, ExPolygons> m_objectBrimAreasByInstance;
// Convex hull of the 1st layer extrusions.
// It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
// It does NOT encompass user extrusions generated by custom G-code,
+37 -7
View File
@@ -980,6 +980,9 @@ void PrintConfigDef::init_common_params()
def->tooltip = L("Select the network agent implementation for printer communication.");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
// Plugin-backed (see ConfigOptionDef::is_plugin_backed), but edited through the Choice widget above
// rather than a plugin_picker field.
def->plugin_type = "printer-connection";
def->set_default_value(new ConfigOptionString(""));
def = this->add("print_host", coString);
@@ -1077,6 +1080,17 @@ void PrintConfigDef::init_common_params()
def = this->add("preset_name", coString);
def->set_default_value(new ConfigOptionString());
}
def = this->add("plugin_config_overrides", coString);
def->label = L("Capabilities");
def->tooltip = L("Configuration for the plugin capabilities this preset uses, overriding the global "
"Capabilities configuration. Stored as a raw JSON array and edited through the dialog "
"behind the button, never typed in directly.");
// Never shown as a text field: GUIType::plugin_config renders a button that opens PluginsConfigDialog.
def->gui_type = ConfigOptionDef::GUIType::plugin_config;
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionString(""));
}
void PrintConfigDef::init_fff_params()
@@ -2273,8 +2287,8 @@ void PrintConfigDef::init_fff_params()
def->label = L("Top surface expansion");
def->category = L("Strength");
def->tooltip = L("Expands the top surfaces by this distance to connect distinct top surfaces and fill gaps.\n"
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane."
"Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top."
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane. "
"Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top. "
"The expansion is applied to the original top surface, before any other processing such as bridging or overhang detection.");
def->sidetext = L("mm");
def->min = 0;
@@ -2849,7 +2863,7 @@ void PrintConfigDef::init_fff_params()
def->nullable = true;
def->min = 0;
def->max = max_temp;
def->sidetext = L(u8"℃" /* °C */); // degrees Celsius, CIS languages need translation
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
def->set_default_value(new ConfigOptionIntsNullable{0});
def = this->add("filament_flush_volumetric_speed", coFloats);
@@ -5488,7 +5502,23 @@ void PrintConfigDef::init_fff_params()
def->height = 5;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString());
def = this->add("plugins", coStrings);
def->label = L("Plugins Used");
def->tooltip = L("Plugin capabilities referenced by this preset, stored as name;uuid;capability.");
def->mode = comDevelop;
def->set_default_value(new ConfigOptionStrings());
def = this->add("slicing_pipeline_plugin", coStrings);
def->label = L("Slicing Pipeline Plugin");
def->tooltip = L("Python plugin(s) invoked at each slicing pipeline step to read and modify intermediate slicing data, "
"including a final G-code post-processing step. Research/experimental.");
def->gui_type = ConfigOptionDef::GUIType::plugin_picker;
def->plugin_type = "slicing-pipeline";
def->full_width = true;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionStrings());
def = this->add("printer_model", coString);
def->label = L("Printer type");
def->tooltip = L("Type of the printer.");
@@ -7930,7 +7960,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Extruder change");
def->tooltip = L("To prevent oozing, the nozzle temperature will be cooled during ramming. Therefore, the ramming time must be greater than the cooldown time. 0 means disabled.");
def->mode = comAdvanced;
def->sidetext = "°C";
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
def->min = 0;
def->nullable = true;
def->set_default_value(new ConfigOptionIntsNullable{0});
@@ -7960,7 +7990,7 @@ void PrintConfigDef::init_fff_params()
def->tooltip = L(
"To prevent oozing, the nozzle temperature will be cooled during ramming. Note: only a cooldown command and fan activation are triggered, reaching the target temperature is not guaranteed. 0 means disabled.");
def->mode = comAdvanced;
def->sidetext = "°C";
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
def->min = 0;
def->nullable = true;
def->set_default_value(new ConfigOptionIntsNullable{0});
@@ -8011,7 +8041,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("filament_preheat_temperature_delta", coFloats);
def->label = L("Preheat temperature delta");
def->tooltip = L("Temperature delta applied during pre-heating before tool change.");
def->sidetext = "°C";
def->sidetext = L(u8"\u2103" /* °C */); // degrees Celsius, CIS languages need translation
def->mode = comDevelop;
def->nullable = true;
def->set_default_value(new ConfigOptionFloatsNullable{0});
+1
View File
@@ -1779,6 +1779,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionBool, ooze_prevention))
((ConfigOptionString, filename_format))
((ConfigOptionStrings, post_process))
((ConfigOptionStrings, slicing_pipeline_plugin))
((ConfigOptionString, printer_model))
((ConfigOptionFloat, resolution))
((ConfigOptionFloats, retraction_minimum_travel))
+1 -4
View File
@@ -376,10 +376,7 @@ SupportGeneratorLayersPtr generate_raft_base(
Polygons trimming;
// BBS: if first layer of support is intersected with object island, it must have the same function as brim unless in nobrim mode.
// brim_object_gap is changed to 0 by default, it's no longer appropriate to use it to determine the gap of first layer support.
//if (object.has_brim())
// trimming = offset(object.layers().front()->lslices, (float)scale_(object.config().brim_object_gap.value), SUPPORT_SURFACES_OFFSET_PARAMETERS);
//else
trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS);
trimming = offset(object.layers().front()->lslices, (float) scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS);
if (inflate_factor_1st_layer > SCALED_EPSILON) {
// Inflate in multiple steps to avoid leaking of the support 1st layer through object walls.
auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width())));
+46 -4
View File
@@ -469,6 +469,31 @@ std::string CalibPressureAdvanceLine::generate_test(double start_pa /*= 0*/, dou
return print_pa_lines(startx, starty, start_pa, step_pa, count);
}
BoundingBoxf CalibPressureAdvanceLine::print_extents(const BoundingBoxf &bed_ext) const
{
BoundingBoxf adjusted_bed = bed_ext;
if (is_delta()) {
CalibPressureAdvanceLine::delta_scale_bed_ext(adjusted_bed);
}
double bed_width = adjusted_bed.size().x();
// m_length_long adjusts for narrow beds – exactly as in generate_test()
double line_long = 40.0 + std::min(bed_width - 120.0, 0.0);
double total_line_len = m_length_short * 2 + line_long;
double start_x = adjusted_bed.min.x() + (bed_width - 2 * m_length_short - line_long - 20.0) / 2.0;
double box_width = m_draw_numbers ? (number_spacing() * 8) : 0.0; // 3.0 * 8 = 24 mm
BoundingBoxf extent;
extent.min.x() = start_x;
extent.max.x() = start_x + total_line_len + m_line_width + box_width;
// Y bounds are the full bed (the caller will inset them by -25)
extent.min.y() = adjusted_bed.min.y();
extent.max.y() = adjusted_bed.max.y();
return extent;
}
bool CalibPressureAdvanceLine::is_delta() const { return mp_gcodegen->config().printable_area.values.size() > 4; }
std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double start_y, double start_pa, double step_pa, int num)
@@ -491,9 +516,10 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
const double slow = CalibPressureAdvance::speed_adjust(m_slow_speed);
std::stringstream gcode;
gcode << mp_gcodegen->writer().travel_to_z(m_height_layer + z_offset);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << (m_height_layer + z_offset) << "\n";
double y_pos = start_y;
// prime line
// Purge/first perimeter - acts as an anchor to the rest of the model
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
gcode << writer.set_pressure_advance(0.0);
auto prime_x = start_x;
gcode << move_to(Vec2d(prime_x, y_pos + (num) * m_space_y), writer);
@@ -503,10 +529,13 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
for (int i = 0; i < num; ++i) {
gcode << writer.set_pressure_advance(start_pa + i * step_pa);
gcode << move_to(Vec2d(start_x, y_pos + i * m_space_y), writer);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
gcode << writer.set_speed(slow);
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short, y_pos + i * m_space_y), e_per_mm * m_length_short);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
gcode << writer.set_speed(fast);
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long, y_pos + i * m_space_y), e_per_mm * m_length_long);
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Custom\n";
gcode << writer.set_speed(slow);
gcode << writer.extrude_to_xy(Vec2d(start_x + m_length_short + m_length_long + m_length_short, y_pos + i * m_space_y),
e_per_mm * m_length_short);
@@ -530,9 +559,15 @@ std::string CalibPressureAdvanceLine::print_pa_lines(double start_x, double star
const auto box_start_x = start_x + m_length_short + m_length_long + m_length_short + m_line_width;
DrawBoxOptArgs default_box_opt_args(2, m_height_layer, m_line_width, fast);
//Draw box
default_box_opt_args.is_filled = true;
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Bottom surface\n";
gcode << draw_box(writer, box_start_x, start_y - m_space_y,
number_spacing() * 8, (num + 1) * m_space_y, default_box_opt_args);
//Ensure numbers are shown on the next layer in gcode processor, as in reality
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) << "\n";
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << (m_height_layer*2 + z_offset) << "\n";
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Top surface\n";
gcode << writer.travel_to_z(m_height_layer*2 + z_offset);
for (int i = 0; i < num; i += 2) {
gcode << draw_number(box_start_x + 3 + m_line_width, y_pos + i * m_space_y + m_space_y / 2, start_pa + i * step_pa, m_draw_digit_mode,
@@ -595,12 +630,16 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
speed_adjust(speed_first_layer()));
// create anchoring frame
//pattern uses outer wall speed/width
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y(), print_size_x(), frame_size_y(), default_box_opt_args);
// create tab for numbers
DrawBoxOptArgs draw_box_opt_args = default_box_opt_args;
draw_box_opt_args.is_filled = true;
draw_box_opt_args.num_perimeters = wall_count();
//draw box as bottom surface, so numbers are clearly visible on top
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Bottom surface\n";
gcode << draw_box(m_writer, m_starting_point.x(), m_starting_point.y() + frame_size_y() + line_spacing_first_layer(),
print_size_x(),
max_numbering_height() + line_spacing_first_layer() + m_glyph_padding_vertical * 2, draw_box_opt_args);
@@ -611,7 +650,9 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
const double zhop_config_value = m_config.option<ConfigOptionFloats>("z_hop")->get_at(0);
const auto accel = accel_perimeter();
// draw pressure advance pattern
// Draw pressure advance pattern
// pattern uses outer wall speed, label it as such
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
for (int i = 0; i < m_num_layers; ++i) {
const double layer_height = height_first_layer() + height_z_offset() + (i * height_layer());
const double zhop_height = layer_height + zhop_config_value;
@@ -643,6 +684,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
m_config.option<ConfigOptionFloats>("filament_flow_ratio")->get_at(0));
// glyph on every other line
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
for (int j = 0; j < num_patterns; j += 2) {
gcode << draw_number(glyph_start_x(j), m_starting_point.y() + frame_size_y() + m_glyph_padding_vertical + line_width(),
m_params.start + (j * m_params.step), m_draw_digit_mode, line_width(), number_e_per_mm,
@@ -692,7 +734,7 @@ CustomGCode::Info CalibPressureAdvancePattern::generate_custom_gcodes(const Dyna
for (int j = 0; j < num_patterns; ++j) {
// increment pressure advance
gcode << m_writer.set_pressure_advance(m_params.start + (j * m_params.step));
gcode << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role) << "Outer wall\n";
for (int k = 0; k < wall_count(); ++k) {
to_x += std::cos(to_radians(m_corner_angle) / 2) * side_length;
to_y += std::sin(to_radians(m_corner_angle) / 2) * side_length;
+3 -1
View File
@@ -254,6 +254,8 @@ class CalibPressureAdvanceLine : public CalibPressureAdvance
public:
CalibPressureAdvanceLine(GCode* gcodegen);
~CalibPressureAdvanceLine(){};
// Return the X‑bounds of the pattern on the given bed.
BoundingBoxf print_extents(const BoundingBoxf &bed_ext) const;
std::string generate_test(double start_pa = 0, double step_pa = 0.002, int count = 50);
@@ -379,4 +381,4 @@ private:
const double m_glyph_padding_vertical{1};
};
} // namespace Slic3r
} // namespace Slic3r
+7
View File
@@ -91,6 +91,13 @@ public:
//
void toggle_top_layer_only_view_range();
//
// Dim previous layers (ORCA, ported from preFlight)
// Whether the layers below the current top layer are rendered darkened while
// scrubbing below the full print, so only the current layer is shown at full brightness.
//
bool is_dim_previous_layers() const;
void set_dim_previous_layers(bool value);
//
// Returns true if the given option is visible.
//
bool is_option_visible(EOptionType type) const;
+3
View File
@@ -19,6 +19,9 @@ struct Settings
EViewType view_type{ EViewType::FeatureType };
ETimeMode time_mode{ ETimeMode::Normal };
bool top_layer_only_view_range{ false };
// ORCA: when enabled, all layers below the current top layer are rendered
// darkened (keeping their color) while scrubbing below the full print (ported from preFlight)
bool dim_previous_layers{ false };
bool spiral_vase_mode{ false };
//
// Required update flags
+10
View File
@@ -72,6 +72,16 @@ void Viewer::toggle_top_layer_only_view_range()
m_impl->toggle_top_layer_only_view_range();
}
bool Viewer::is_dim_previous_layers() const
{
return m_impl->is_dim_previous_layers();
}
void Viewer::set_dim_previous_layers(bool value)
{
m_impl->set_dim_previous_layers(value);
}
bool Viewer::is_option_visible(EOptionType type) const
{
return m_impl->is_option_visible(type);
+46 -5
View File
@@ -1223,6 +1223,20 @@ static float encode_color(const Color& color) {
return static_cast<float>(i_color);
}
// ORCA: how much the layers below the current top layer are darkened when
// Settings::dim_previous_layers is enabled (ported from preFlight). 0.0 = no change, 1.0 = black.
static constexpr float PREVIOUS_LAYER_DARKEN_FACTOR = 0.60f;
// ORCA: returns the encoded color scaled towards black by 'factor', preserving its hue
static float encode_color_darkened(const Color& color, float factor) {
const float keep = 1.0f - factor;
const int r = static_cast<int>(color[0] * keep);
const int g = static_cast<int>(color[1] * keep);
const int b = static_cast<int>(color[2] * keep);
const int i_color = r << 16 | g << 8 | b;
return static_cast<float>(i_color);
}
void ViewerImpl::update_colors_texture()
{
@@ -1234,14 +1248,30 @@ void ViewerImpl::update_colors_texture()
const size_t top_layer_id = m_settings.top_layer_only_view_range ? m_layers.get_view_range()[1] : 0;
const bool color_top_layer_only = m_view_range.get_full()[1] != m_view_range.get_visible()[1];
// ORCA: when dim_previous_layers is enabled, darken every layer below the current top layer
// (keeping its color) whenever we are not rendering the whole print, so that only the layer
// being scrubbed to is shown at full brightness (ported from preFlight). This shares
// top_layer_id with the greying path, so it only applies while in top-layer-only mode - that
// way the moves slider still animates normally across all layers when that mode is disabled.
const bool dim_previous_layers = m_settings.dim_previous_layers && !m_layers.empty();
const bool full_render = (m_layers.get_view_range()[0] == 0) &&
(m_layers.get_view_range()[1] >= static_cast<uint32_t>(m_layers.count()) - 1) &&
(m_view_range.get_visible()[1] == m_view_range.get_full()[1]);
// Based on current settings and slider position, we might want to render some
// vertices as dark grey. Use either that or the normal color (from the cache).
// vertices as dark grey (or darkened, see above). Use either that or the normal color (from the cache).
std::vector<float> colors(m_vertices_colors.size());
assert(colors.size() == m_vertices.size() && m_vertices_colors.size() == m_vertices.size());
for (size_t i=0; i<m_vertices.size(); ++i)
colors[i] = (color_top_layer_only && m_vertices[i].layer_id < top_layer_id &&
(!m_settings.spiral_vase_mode || i != m_view_range.get_enabled()[0])) ?
encode_color(DUMMY_COLOR) : m_vertices_colors[i];
for (size_t i=0; i<m_vertices.size(); ++i) {
const PathVertex& v = m_vertices[i];
const bool keep_spiral_seam = m_settings.spiral_vase_mode && i == m_view_range.get_enabled()[0];
if (dim_previous_layers && !full_render && v.layer_id < top_layer_id && !keep_spiral_seam)
colors[i] = encode_color_darkened(get_vertex_color(v), PREVIOUS_LAYER_DARKEN_FACTOR);
else if (color_top_layer_only && v.layer_id < top_layer_id && !keep_spiral_seam)
colors[i] = encode_color(DUMMY_COLOR);
else
colors[i] = m_vertices_colors[i];
}
#ifdef ENABLE_OPENGL_ES
if (!colors.empty())
@@ -1349,6 +1379,17 @@ void ViewerImpl::toggle_top_layer_only_view_range()
update_colors_texture();
}
// ORCA: enable/disable darkening of the layers below the current top layer (ported from preFlight)
void ViewerImpl::set_dim_previous_layers(bool value)
{
if (m_settings.dim_previous_layers == value)
return;
m_settings.dim_previous_layers = value;
// defer the actual color/texture rebuild to the next render(), when the GL context is current
// (this may be toggled from the Preferences dialog, outside the canvas context)
m_settings.update_colors = true;
}
std::vector<ETimeMode> ViewerImpl::get_time_modes() const
{
std::vector<ETimeMode> ret;
+4
View File
@@ -85,6 +85,10 @@ public:
bool is_top_layer_only_view_range() const { return m_settings.top_layer_only_view_range; }
void toggle_top_layer_only_view_range();
// ORCA: darken layers below the current top layer while scrubbing (ported from preFlight)
bool is_dim_previous_layers() const { return m_settings.dim_previous_layers; }
void set_dim_previous_layers(bool value);
bool is_spiral_vase_mode() const { return m_settings.spiral_vase_mode; }
std::vector<ETimeMode> get_time_modes() const;
+110 -1
View File
@@ -23,6 +23,7 @@ set(SLIC3R_GUI_SOURCES
GUI/AboutDialog.cpp
GUI/AboutDialog.hpp
GUI/AmsMappingPopup.cpp
GUI/AmsMappingPopupUpdate.cpp
GUI/AmsMappingPopup.hpp
GUI/AMSMaterialsSetting.cpp
GUI/AMSMaterialsSetting.hpp
@@ -40,6 +41,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Auxiliary.hpp
GUI/BackgroundSlicingProcess.cpp
GUI/BackgroundSlicingProcess.hpp
GUI/PostProcessor.cpp
GUI/PostProcessor.hpp
GUI/BBLStatusBarBind.cpp
GUI/BBLStatusBarBind.hpp
GUI/BBLStatusBar.cpp
@@ -109,6 +112,27 @@ set(SLIC3R_GUI_SOURCES
GUI/Downloader.hpp
GUI/DownloadProgressDialog.cpp
GUI/DownloadProgressDialog.hpp
GUI/PluginSource.hpp
GUI/PluginSort.hpp
GUI/PluginStatus.hpp
GUI/PluginPickerDialog.cpp
GUI/PluginPickerDialog.hpp
GUI/PluginsDialog.cpp
GUI/PluginsDialog.hpp
GUI/SpeedDialDialog.cpp
GUI/SpeedDialDialog.hpp
GUI/ActionRegistry.cpp
GUI/ActionRegistry.hpp
GUI/PluginsConfigDialog.cpp
GUI/PluginsConfigDialog.hpp
GUI/ProcessRunner.cpp
GUI/ProcessRunner.hpp
GUI/TerminalDialog.cpp
GUI/TerminalDialog.hpp
GUI/PluginProgressDialog.cpp
GUI/PluginProgressDialog.hpp
GUI/PluginWebDialog.cpp
GUI/PluginWebDialog.hpp
GUI/DragCanvas.cpp
GUI/DragCanvas.hpp
GUI/EditGCodeDialog.cpp
@@ -582,11 +606,58 @@ set(SLIC3R_GUI_SOURCES
GUI/Widgets/TempInput.hpp
GUI/Widgets/TextInput.cpp
GUI/Widgets/TextInput.hpp
GUI/Widgets/WebViewHostDialog.cpp
GUI/Widgets/WebViewHostDialog.hpp
GUI/Widgets/WebView.cpp
GUI/Widgets/WebView.hpp
GUI/WipeTowerDialog.cpp
GUI/wxExtensions.cpp
GUI/wxExtensions.hpp
plugin/PythonInterpreter.cpp
plugin/PythonInterpreter.hpp
plugin/PythonPluginBridge.cpp
plugin/PythonPluginBridge.hpp
plugin/PythonPluginInterface.hpp
plugin/PyPluginPackage.hpp
plugin/PluginBindingUtils.hpp
plugin/host/PluginHost.cpp
plugin/host/PluginHost.hpp
plugin/host/PluginHostBindings.hpp
plugin/host/PluginHostApp.cpp
plugin/host/PluginHostGeometry.cpp
plugin/host/PluginHostMesh.cpp
plugin/host/PluginHostMesh.hpp
plugin/host/PluginHostModel.cpp
plugin/host/PluginHostPresets.cpp
plugin/host/PluginHostSlicing.cpp
plugin/host/PluginHostUi.cpp
plugin/host/PluginHostUi.hpp
plugin/CloudPluginService.cpp
plugin/CloudPluginService.hpp
plugin/PluginFsUtils.cpp
plugin/PluginFsUtils.hpp
plugin/PluginConfig.cpp
plugin/PluginConfig.hpp
plugin/PluginLoader.cpp
plugin/PluginLoader.hpp
plugin/PluginDescriptor.hpp
plugin/PluginHooks.cpp
plugin/PluginHooks.hpp
plugin/PluginManager.cpp
plugin/PluginManager.hpp
plugin/PluginAuditManager.cpp
plugin/PluginAuditManager.hpp
plugin/PluginResolver.cpp
plugin/PluginResolver.hpp
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp
plugin/pluginTypes/script/ScriptPluginCapability.hpp
plugin/pluginTypes/script/ScriptPluginCapability.cpp
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp
plugin/pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp
plugin/pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.cpp
plugin/pluginTypes/slicingPipeline/SlicingPipelinePluginCapabilityTrampoline.hpp
pchheader.cpp
pchheader.hpp
Utils/ASCIIFolding.cpp
@@ -782,8 +853,16 @@ else()
set(_opengl_link_lib OpenGL::GL)
endif()
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise)
target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo minilzo libvgcode md4c-html glad ${_opengl_link_lib} hidapi mdns ${wxWidgets_LIBRARIES} glfw libcurl OpenSSL::SSL OpenSSL::Crypto noise::noise pybind11::embed)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
# Linux finds wxWidgets in module mode, whose include dirs and definitions
# only apply at src/ directory scope; export them from the target so wx
# headers reach GUI-header consumers outside src/ (tests). The CONFIG-mode
# wx::* imported targets used on Windows/macOS already propagate these.
target_include_directories(libslic3r_gui SYSTEM PUBLIC ${wxWidgets_INCLUDE_DIRS})
target_compile_definitions(libslic3r_gui PUBLIC ${wxWidgets_DEFINITIONS} $<$<CONFIG:Debug>:${wxWidgets_DEFINITIONS_DEBUG}>)
endif ()
if (MSVC)
target_link_libraries(libslic3r_gui Setupapi.lib)
@@ -861,3 +940,33 @@ endif ()
# Add a definition so that we can tell we are compiling slic3r.
target_compile_definitions(libslic3r_gui PRIVATE SLIC3R_CURRENTLY_COMPILING_GUI_MODULE)
if(ORCA_BUNDLED_UV_EXECUTABLE_CONFIG)
target_compile_definitions(libslic3r_gui PRIVATE "ORCA_BUNDLED_UV_EXECUTABLE=\"${ORCA_BUNDLED_UV_EXECUTABLE_CONFIG}\"")
endif()
if (ORCA_BUILD_PYTHON_STUBGEN_MODULE)
add_library(orca_stubgen MODULE
plugin/PythonPluginBridge.cpp
)
target_compile_definitions(orca_stubgen PRIVATE
ORCA_PYTHON_STUBGEN_MODULE
)
target_include_directories(orca_stubgen PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
Utils
)
target_link_libraries(orca_stubgen PRIVATE
libslic3r_gui
pybind11::embed
)
set_target_properties(orca_stubgen PROPERTIES
OUTPUT_NAME orca
PREFIX ""
)
endif()
+1 -1
View File
@@ -548,7 +548,7 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
}
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
+7 -7
View File
@@ -42,7 +42,7 @@ static std::string get_humidity_level_img_path(int humidity_percent)
}
if (wxGetApp().dark_mode()) {
return "hum_level" + std::to_string(hum_level) + "_no_num_dark";
return "hum_level" + std::to_string(hum_level) + "_no_num_dark"; // Orca: use the dark-mode humidity glyph in dark mode
} else {
return "hum_level" + std::to_string(hum_level) + "_no_num_light";
}
@@ -86,7 +86,7 @@ FilamentItemPanel::FilamentItemPanel(wxWindow* parent, const wxString& text, con
: wxPanel(parent, id)
, m_icon_name(icon_name)
{
SetBackgroundColour(wxColour("#F0F0F1")); // Light gray background
SetBackgroundColour(wxColour("#F0F0F1")); // Orca: light-gray panel scheme (#F0F0F1 replaces REF #F7F7F7)
SetMinSize(wxSize(FromDIP(64), FromDIP(106))); // Width: 64, Height: 106
SetSize(wxSize(FromDIP(64), FromDIP(106))); // Fixed size
@@ -454,7 +454,7 @@ wxBoxSizer* AMSDryCtrWin::create_normal_state_panel(wxPanel* parent)
m_temperature_input->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
m_temperature_input->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
Label* temp_unit_label = new Label(parent, wxString::FromUTF8("℃"));
Label* temp_unit_label = new Label(parent, wxString::FromUTF8(u8"\u2103" /* °C */));
temp_unit_label->SetForegroundColour(*wxBLACK);
temp_sizer->Add(m_temperature_input, 1, wxRIGHT, FromDIP(1));
temp_sizer->Add(temp_unit_label, 0, wxALIGN_CENTER_VERTICAL);
@@ -977,7 +977,7 @@ void AMSDryCtrWin::create()
// set title icon
SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
this->SetDoubleBuffered(true);
std::string icon_path = (boost::format("%1%/images/OrcaSlicerTitle.ico") % resources_dir()).str();
std::string icon_path = (boost::format("%1%/images/OrcaSlicerTitle.ico") % resources_dir()).str(); // Orca: app title icon
SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
SetSize(wxSize(FromDIP(700), FromDIP(500)));
@@ -1444,9 +1444,9 @@ int AMSDryCtrWin::update_ams_change(DevAms* dev_ams)
m_ams_info.m_ams_id = dev_ams->GetAmsId();
if (dev_ams->GetAmsType() == DevAmsType::N3F) {
m_temperature_input->SetHint("45-65" + wxString::FromUTF8("°C"));
m_temperature_input->SetHint("45-65" + wxString::FromUTF8(u8"\u2103" /* °C */));
} else if (dev_ams->GetAmsType() == DevAmsType::N3S) {
m_temperature_input->SetHint("45-85" + wxString::FromUTF8("°C"));
m_temperature_input->SetHint("45-85" + wxString::FromUTF8(u8"\u2103" /* °C */));
}
m_time_input->SetHint("1-24 h");
@@ -1467,7 +1467,7 @@ int AMSDryCtrWin::update_dryness_status(DevAms* dev_ams)
if (m_ams_info.m_temperature != dev_ams->GetCurrentTemperature()) {
updated += 1;
m_ams_info.m_temperature = dev_ams->GetCurrentTemperature();
m_temperature_data_label->SetLabel(std::to_string(m_ams_info.m_temperature) + wxString::FromUTF8("°C"));
m_temperature_data_label->SetLabel(std::to_string(m_ams_info.m_temperature) + wxString::FromUTF8(u8"\u2103" /* °C */));
}
if (is_dry_ctr_idle(dev_ams)) {
+1 -1
View File
@@ -6,7 +6,7 @@
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/I18N.hpp" // Orca: explicit _L() catalog include
#include <chrono>
#include <optional>
+15 -5
View File
@@ -296,11 +296,8 @@ void AMSMaterialsSetting::create_panel_kn(wxWindow* parent)
m_ratio_text->SetForegroundColour(wxColour(50, 58, 61));
m_ratio_text->SetFont(Label::Head_14);
std::string language = wxGetApp().app_config->get("language");
wxString region = "en";
if (language.find("zh") == 0)
region = "zh";
wxString link_url = wxString::Format("https://wiki.bambulab.com/%s/software/bambu-studio/calibration_pa", region);
// Orca: link to the Orca Slicer pressure-advance wiki (region-agnostic).
wxString link_url = "https://www.orcaslicer.com/wiki/pressure_advance_calib";
m_wiki_ctrl = new HyperLink(parent, _L("Wiki Guide"), link_url);
cali_title_sizer->Add(m_ratio_text, 0, wxALIGN_CENTER_VERTICAL);
cali_title_sizer->Add(m_wiki_ctrl, 0, wxALIGN_CENTER_VERTICAL);
@@ -449,9 +446,19 @@ void AMSMaterialsSetting::update_filament_editing(bool is_printing)
m_tip_readonly->Wrap(FromDIP(380));
m_tip_readonly->Show(is_printing);
}
if (m_view_only) { // Orca: view-only (2D laser/cut) — lock every edit control and hide apply/reset
m_comboBox_filament->Enable(false);
m_comboBox_cali_result->Enable(false);
m_input_k_val->Enable(false);
m_input_n_val->Enable(false);
m_button_confirm->Hide();
m_button_reset->Hide();
}
}
void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
if (m_view_only) return; // Orca: view-only never commits
MessageDialog msg_dlg(nullptr, _L("Are you sure you want to clear the filament information?"), wxEmptyString, wxICON_WARNING | wxOK | wxCANCEL);
auto result = msg_dlg.ShowModal();
if (result != wxID_OK)
@@ -619,6 +626,8 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
if (!obj)
return;
if (m_view_only) return; // Orca: view-only never commits
//get filament id
ams_filament_id = "";
ams_setting_id = "";
@@ -830,6 +839,7 @@ void AMSMaterialsSetting::on_picker_color(wxCommandEvent& event)
void AMSMaterialsSetting::on_clr_picker(wxMouseEvent &event)
{
if (m_view_only) return; // Orca: view-only disables color editing
if(!m_is_third)
return;
+4
View File
@@ -124,6 +124,9 @@ public:
std::string ams_setting_id;
bool m_is_third;
// Orca: view-only mode (laser/cut). When set, the dialog is inspectable but every
// editing control is disabled and no command is sent.
bool m_view_only = false;
wxString m_brand_filament;
wxString m_brand_sn;
wxString m_brand_tmp;
@@ -183,6 +186,7 @@ protected:
int m_pa_cali_select_id = 0;
// Orca: native wxComboBox on macOS (the custom ComboBox mis-renders the filament dropdown there)
#ifdef __APPLE__
wxComboBox *m_comboBox_filament;
#else
+3 -2
View File
@@ -272,7 +272,7 @@ void AMSSetting::create()
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(10));
m_sizer_main->Add(m_static_ams_settings, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(24));
m_sizer_main->Add(0, 0, 0, wxTOP, FromDIP(10));
m_sizer_main->Add(m_panel_body, 1, wxBottom | wxLEFT | wxRIGHT | wxEXPAND, FromDIP(24));
m_sizer_main->Add(m_panel_body, 1, wxBOTTOM | wxLEFT | wxRIGHT | wxEXPAND, FromDIP(24)); // Orca: wxBOTTOM (REF uses the wxEdge enum by mistake)
this->SetSizer(m_sizer_main);
this->Layout();
@@ -644,7 +644,7 @@ void AMSSettingTypePanel::Update(const MachineObject* obj)
}
if (ptr->IsSwitching()) {
int display_percent = obj->get_upgrade_percent();
int display_percent = obj->get_upgrade_percent(); // Orca: read upgrade progress via the kept MachineObject accessor
if (display_percent == 100 || display_percent == 0) {
display_percent = 1;// special case, sometimes it's switching but percent is 0 or 100
}
@@ -735,6 +735,7 @@ void AMSSettingTypePanel::OnAmsTypeChanged(wxCommandEvent& event)
event.Skip();
}
// Orca: AMSSettingArrangeAMSOrder impl kept compiled-out (feature intentionally not shipped).
#if 0 /*used option*/
AMSSettingArrangeAMSOrder::AMSSettingArrangeAMSOrder(wxWindow* parent)
: wxPanel(parent)
+1
View File
@@ -129,6 +129,7 @@ private:
AnimaIcon* m_switching_icon;
};
// Orca: the firmware AMS-reorder ("Arrange AMS Order") panel is intentionally not shipped.
#if 0
class AMSSettingArrangeAMSOrder : public wxPanel
{
+390
View File
@@ -0,0 +1,390 @@
#include "ActionRegistry.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "slic3r/plugin/PluginManager.hpp"
#include <libslic3r/AppConfig.hpp>
#include <slic3r/plugin/PythonPluginInterface.hpp>
#include <wx/thread.h>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <unordered_map>
namespace Slic3r { namespace GUI {
namespace {
constexpr const char* kConfigSection = "speed_dial";
nlohmann::json parse_config_json(const std::string& value, nlohmann::json fallback)
{
nlohmann::json parsed = nlohmann::json::parse(value, nullptr, false);
return parsed.is_discarded() ? std::move(fallback) : parsed;
}
nlohmann::json read_section(const char* key, nlohmann::json fallback)
{
return parse_config_json(wxGetApp().app_config->get(kConfigSection, key), std::move(fallback));
}
void write_section(const char* key, const nlohmann::json& j)
{
wxGetApp().app_config->set(kConfigSection, key, j.dump());
}
std::vector<std::string> read_string_array(const char* key)
{
auto j = read_section(key, nlohmann::json::array());
std::vector<std::string> v;
for (auto& e : j)
if (e.is_string())
v.push_back(e.get<std::string>());
return v;
}
// frecency = frequency + recency; score halves every 30 idle days.
double frecency_score(int count, long long last, long long now)
{
if (count <= 0)
return 0.0;
constexpr double HALF_LIFE_DAYS = 30.0;
double age = std::max(0.0, double(now - last) / 86400.0);
return count * std::pow(2.0, -age / HALF_LIFE_DAYS);
}
// ---- script-plugin action source (the one and only source) ------------------
std::string find_loaded_source_name(PluginManager& manager, const std::string& plugin_key)
{
PluginDescriptor descriptor;
if (manager.try_get_plugin_descriptor(plugin_key, descriptor) && !descriptor.name.empty())
return descriptor.name;
return plugin_key;
}
// A runnable script capability exposed as a speed-dial action. source_key = plugin_key
// (identity), so a plugin display-name change does not re-key the action.
struct PluginScriptAction : AppAction
{
static constexpr const char* kIdPrefix = "plugin_script_action";
std::string plugin_key;
std::string capability;
// The id an action for (plugin_key, capability) would have - lets refresh_capability
// remove a gone capability without materialising the action.
static std::string id_for(const std::string& plugin_key, const std::string& capability)
{
return AppAction::compose_id(kIdPrefix, capability.empty() ? plugin_key : capability, plugin_key);
}
PluginScriptAction(std::string plugin_key_in, std::string capability_in, std::string source_name)
: AppAction(kIdPrefix,
capability_in.empty() ? plugin_key_in : capability_in, // title
plugin_key_in, // source_key
std::move(source_name)),
plugin_key(std::move(plugin_key_in)), capability(std::move(capability_in))
{}
AppActionRunResult run() const override
{
std::string error;
const ExecutionResult result = PluginManager::instance().run_script_capability(plugin_key, capability, error);
if (!error.empty())
return {AppActionRunResult::Level::Error, from_u8(error)};
const bool skipped = result.status == PluginResult::Skipped;
const wxString fallback = skipped ? _L("Script plugin skipped.") : _L("Script plugin finished.");
return {skipped ? AppActionRunResult::Level::Info : AppActionRunResult::Level::Success,
result.message.empty() ? fallback : from_u8(result.message)};
}
};
// Builds an action for a capability, or nullptr if it is not a currently-loaded,
// enabled script capability.
std::unique_ptr<AppAction> make_action(const std::string& plugin_key, const std::string& capability,
const std::string& source_name)
{
PluginManager& manager = PluginManager::instance();
if (!manager.is_plugin_loaded(plugin_key))
return nullptr;
// only_enabled defaults true, so a disabled capability resolves to nullptr here.
if (!manager.get_plugin_capability({PluginCapabilityType::Script, capability, plugin_key}))
return nullptr;
return std::make_unique<PluginScriptAction>(plugin_key, capability, source_name);
}
} // namespace
void ActionRegistry::init()
{
assert(wxThread::IsMain());
assert(!m_started);
m_started = true;
PluginManager& manager = PluginManager::instance();
auto on_source = [this](const std::string& plugin_key, ActionChange change) {
if (!wxTheApp || wxGetApp().is_closing())
return;
wxGetApp().CallAfter([this, plugin_key, change] {
if (!wxGetApp().is_closing())
this->refresh_source(plugin_key, change);
});
};
auto on_capability = [this](const PluginCapabilityId& capability, ActionChange change) {
if (capability.type != PluginCapabilityType::Script || !wxTheApp || wxGetApp().is_closing())
return;
const std::string plugin_key = capability.plugin_key;
const std::string name = capability.name;
wxGetApp().CallAfter([this, plugin_key, name, change] {
if (!wxGetApp().is_closing())
this->refresh_capability(plugin_key, name, change);
});
};
// Subscribe before enumerating so a concurrent load cannot land between the initial
// snapshot and callback registration. Duplicate notifications are safe: upsert is by
// id and the m_actions scan in refresh_source is idempotent.
manager.subscribe_on_load_callback(
[on_source](const std::string& key) { on_source(key, ActionChange::Added); });
manager.subscribe_on_unload_callback(
[on_source](const std::string& key) { on_source(key, ActionChange::Removed); });
manager.subscribe_on_capability_load_callback(
[on_capability](const PluginCapabilityId& capability) {
on_capability(capability, ActionChange::Added);
});
manager.subscribe_on_capability_unload_callback(
[on_capability](const PluginCapabilityId& capability) {
on_capability(capability, ActionChange::Removed);
});
// enumerate current script capabilities
std::unordered_map<std::string, std::string> source_names;
for (const PluginDescriptor& desc : manager.get_plugin_descriptors())
if (!desc.name.empty())
source_names.emplace(desc.plugin_key, desc.name);
for (const auto& capability : manager.get_plugin_capabilities("", PluginCapabilityType::Script)) {
if (!capability)
continue;
const std::string& key = capability->audit_plugin_key();
auto it = source_names.find(key);
const std::string& source_name = it == source_names.end() ? key : it->second;
if (auto action = make_action(key, capability->name(), source_name))
upsert(std::move(action));
}
}
void ActionRegistry::refresh_source(const std::string& plugin_key, ActionChange change)
{
assert(wxThread::IsMain());
// Remove this source's current actions. Collect first - erasing from m_actions while
// iterating invalidates the iterator. why: m_actions (not the loader) is the source of
// truth, so this is correct even after the plugin has already unloaded.
std::vector<std::string> stale;
for (const auto& [id, action] : m_actions)
if (action->source_key() == plugin_key)
stale.push_back(id);
for (const std::string& id : stale)
remove(id);
if (change == ActionChange::Removed)
return;
PluginManager& manager = PluginManager::instance();
const std::string source_name = find_loaded_source_name(manager, plugin_key);
for (const auto& capability : manager.get_plugin_capabilities(plugin_key, PluginCapabilityType::Script)) {
if (!capability)
continue;
if (auto action = make_action(plugin_key, capability->name(), source_name))
upsert(std::move(action));
}
}
void ActionRegistry::refresh_capability(const std::string& plugin_key, const std::string& capability,
ActionChange change)
{
assert(wxThread::IsMain());
const std::string id = PluginScriptAction::id_for(plugin_key, capability);
if (change == ActionChange::Removed) {
remove(id);
return;
}
PluginManager& manager = PluginManager::instance();
if (auto action = make_action(plugin_key, capability, find_loaded_source_name(manager, plugin_key)))
upsert(std::move(action));
else
remove(id);
}
void ActionRegistry::upsert(std::unique_ptr<AppAction> action)
{
assert(wxThread::IsMain());
if (!action)
return;
seed_state(*action);
std::string id = action->id();
std::shared_ptr<AppAction> stored = std::move(action);
m_actions.insert_or_assign(std::move(id), std::move(stored));
}
void ActionRegistry::remove(const std::string& id)
{
assert(wxThread::IsMain());
m_actions.erase(id);
}
void ActionRegistry::seed_state(AppAction& a) const
{
auto favs = read_string_array("favourite_actions");
a.favourite = std::find(favs.begin(), favs.end(), a.id()) != favs.end();
nlohmann::json stats = read_section("stats", nlohmann::json::object());
auto it = stats.find(a.id());
if (it != stats.end() && it->is_object()) {
a.count = it->value("count", 0);
a.last = it->value("last", 0LL);
} else {
a.count = 0;
a.last = 0;
}
}
// ---- read surface -----------------------------------------------------------
const AppAction* ActionRegistry::by_id(const std::string& id) const
{
assert(wxThread::IsMain());
auto it = m_actions.find(id);
return it == m_actions.end() ? nullptr : it->second.get();
}
AppAction* ActionRegistry::find(const std::string& id)
{
return const_cast<AppAction*>(by_id(id));
}
// ---- dispatch + write-through ----------------------------------------------
AppActionRunResult ActionRegistry::run(const std::string& id)
{
assert(wxThread::IsMain());
auto it = m_actions.find(id);
if (it == m_actions.end())
return {}; // default Info, empty message
// why: hold a shared_ptr keep-alive, never a bare map entry. A runner may pump a
// nested event loop; a queued source refresh can erase the entry while the
// keep-alive preserves the action until run returns.
std::shared_ptr<AppAction> keep = it->second;
AppActionRunResult o = keep->run();
if (o.level == AppActionRunResult::Level::Busy)
return o;
// Bump stats (write-through). Re-read to avoid clobbering a concurrent field.
nlohmann::json stats = read_section("stats", nlohmann::json::object());
if (!stats.is_object()) // corrupt (valid-JSON, non-object) value degrades to empty
stats = nlohmann::json::object();
nlohmann::json& e = stats[id];
if (!e.is_object())
e = nlohmann::json::object();
e["count"] = e.value("count", 0) + 1;
e["last"] = (long long) std::time(nullptr);
write_section("stats", stats);
if (AppAction* live = find(id)) { live->count = e["count"]; live->last = e["last"]; }
return o;
}
void ActionRegistry::set_favourite(const std::string& id, bool on)
{
assert(wxThread::IsMain());
auto favs = read_string_array("favourite_actions");
auto it = std::find(favs.begin(), favs.end(), id);
if (on && it == favs.end())
favs.push_back(id);
if (!on && it != favs.end())
favs.erase(it);
write_section("favourite_actions", nlohmann::json(favs));
if (AppAction* live = find(id))
live->favourite = on;
}
void ActionRegistry::reorder_favourites(const std::vector<std::string>& ids)
{
assert(wxThread::IsMain());
auto cur = read_string_array("favourite_actions");
std::vector<std::string> next;
// keep the requested order, but only ids that are actually favourites (guard a bad payload)
for (const auto& id : ids)
if (std::find(cur.begin(), cur.end(), id) != cur.end() &&
std::find(next.begin(), next.end(), id) == next.end())
next.push_back(id);
// why: don't drop favourites the page omitted (e.g. pins with no live action hidden from the bar)
for (const auto& id : cur)
if (std::find(next.begin(), next.end(), id) == next.end())
next.push_back(id);
write_section("favourite_actions", nlohmann::json(next));
}
bool ActionRegistry::should_ask(const std::string& id) const
{
assert(wxThread::IsMain());
auto arr = read_string_array("ask_suppressed");
return std::find(arr.begin(), arr.end(), id) == arr.end();
}
void ActionRegistry::suppress_ask(const std::string& id)
{
assert(wxThread::IsMain());
auto arr = read_string_array("ask_suppressed");
if (std::find(arr.begin(), arr.end(), id) == arr.end())
arr.push_back(id);
write_section("ask_suppressed", nlohmann::json(arr));
}
// ---- snapshot ---------------------------------------------------------------
nlohmann::json ActionRegistry::snapshot() const
{
assert(wxThread::IsMain());
std::vector<const AppAction*> sorted;
sorted.reserve(m_actions.size());
for (const auto& entry : m_actions)
sorted.push_back(entry.second.get());
const long long now = (long long) std::time(nullptr);
std::sort(sorted.begin(), sorted.end(), [&](const AppAction* a, const AppAction* b) {
double sa = frecency_score(a->count, a->last, now);
double sb = frecency_score(b->count, b->last, now);
if (sa != sb)
return sa > sb;
if (a->title() != b->title())
return a->title() < b->title();
if (a->source_name() != b->source_name())
return a->source_name() < b->source_name();
return a->id() < b->id();
});
nlohmann::json actions = nlohmann::json::array();
for (const AppAction* a : sorted)
actions.push_back({{"id", a->id()},
{"title", a->title()},
{"source", a->source_name()},
{"shortcut", ""}});
// why: favourites is the ORDERED pin list - it must come from favourite_actions
// as stored, not be re-derived from the frecency-sorted actions (that would
// reorder the favourites bar). The page (js) filters out ids with no live action itself.
nlohmann::json favourites(read_string_array("favourite_actions"));
return {{"actions", std::move(actions)}, {"favourites", std::move(favourites)}};
}
}} // namespace Slic3r::GUI
+135
View File
@@ -0,0 +1,135 @@
#pragma once
#include <nlohmann/json.hpp>
#include <wx/string.h>
#include <wx/thread.h>
#include <cassert>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
namespace Slic3r { namespace GUI {
// How a source's action set changed. Drives the registry's refresh handlers.
enum class ActionChange { Added, Removed };
// Result of running an AppAction, in the action layer's own vocabulary. Concrete
// actions translate their runner-specific result into this generic shape.
struct AppActionRunResult
{
enum class Level { Success, Info, Error, Busy };
Level level = Level::Info;
wxString message; // empty = "nothing worth showing"
};
// A speed-dial action: identity + user-state seeded from config + how to run itself.
// Abstract base - the only virtual is run(); concrete subclasses know how to run
// and what their source is.
// note: named AppAction, not Action - Slic3r::GUI::Action is already taken by
// UnsavedChangesDialog's exit-action enum, and this header reaches most GUI TUs.
struct AppAction
{
const std::string& id() const { return m_id; }
const std::string& title() const { return m_title; }
const std::string& source_key() const { return m_source_key; } // stable source identity
const std::string& source_name() const { return m_source_name; } // source display name
// Builds the stable id "<prefix>:<title>:<source_key>". why: one place owns the
// format - both the base ctor and the one raw-key lookup (removing a capability
// without an action object) go through this; ids are never parsed back apart.
static std::string compose_id(std::string_view prefix, std::string_view title, std::string_view source_key)
{
std::string out;
out.reserve(prefix.size() + title.size() + source_key.size() + 2);
out.append(prefix).append(1, ':').append(title).append(1, ':').append(source_key);
return out;
}
// seeded from AppConfig for the snapshot / sort:
bool favourite = false;
int count = 0;
long long last = 0; // epoch seconds
virtual ~AppAction() = default;
virtual AppActionRunResult run() const = 0; // re-resolves + runs (UI thread)
protected:
// The definition is constructor-set and immutable. Refreshes replace an action
// instead of mutating identity after the registry has indexed it by id.
// why: source_key (not the display name) carries identity, so renaming the source's
// display name leaves the id - and its persisted stats/favourite - intact.
AppAction(std::string_view prefix, std::string title, std::string source_key, std::string source_name)
: m_id(compose_id(prefix, title, source_key)),
m_title(std::move(title)),
m_source_key(std::move(source_key)),
m_source_name(std::move(source_name)) {}
private:
std::string m_id; // <prefix>:<title>:<source_key> - stable identity + AppConfig key
std::string m_title; // display name
std::string m_source_key; // stable identity of the action's source (e.g. plugin_key)
std::string m_source_name; // display name of the action's source
};
// Self-contained sink and single owner of runnable actions for the app session.
//
// Workflow:
// 1. init() (once, UI thread) subscribes to the plugin loader and enumerates the
// current script capabilities into actions.
// 2. Loader load/unload callbacks route through refresh_source()/refresh_capability(),
// which upsert()/remove() actions. The registry keeps the only action list and
// restores persisted user state as actions arrive.
// 3. Consumers use by_id(), snapshot(), and run() without knowing the source.
//
// note: there is exactly one source (script plugins), so it lives inline here rather
// than behind a polymorphic source interface.
class ActionRegistry
{
public:
// Subscribes to the plugin loader and enumerates its current actions. Call once
// on the UI thread after the plugin system is up; wires the initial list and live
// updates together.
void init();
// Takes ownership, seeds persisted state, then inserts the action or replaces
// the action with the same id. A null action is ignored.
void upsert(std::unique_ptr<AppAction> action);
// Removes the action with this id. Missing ids are a harmless no-op.
void remove(const std::string& id);
// Always-clean read surface. UI thread only.
const AppAction* by_id(const std::string& id) const;
// Dispatch + write-through (registry is the only thing that touches AppConfig).
AppActionRunResult run(const std::string& id); // runs + bumps stats
void set_favourite(const std::string& id, bool on);
void reorder_favourites(const std::vector<std::string>& ids); // persist a new bar order
// Run-confirm gate, keyed by action id (per-action "don't ask again").
bool should_ask(const std::string& id) const;
void suppress_ask(const std::string& id);
// Flat, frecency-sorted snapshot for the webview: {actions:[...], favourites:[...]}.
nlohmann::json snapshot() const;
private:
void seed_state(AppAction& a) const; // favourite/stats from config
AppAction* find(const std::string& id);
// Loader callbacks (marshalled to the UI thread) land here. refresh_source rebuilds
// one plugin's whole action set; refresh_capability touches a single capability.
void refresh_source(const std::string& plugin_key, ActionChange change);
void refresh_capability(const std::string& plugin_key, const std::string& capability, ActionChange change);
bool m_started = false; // init() runs exactly once; guards double-subscription
std::unordered_map<std::string, std::shared_ptr<AppAction>> m_actions; // UI-thread confined; no lock
};
}} // namespace Slic3r::GUI
File diff suppressed because it is too large Load Diff
+112 -46
View File
@@ -34,23 +34,28 @@
#include "Widgets/CheckBox.hpp"
#include "Widgets/ComboBox.hpp"
#include "Widgets/ScrolledWindow.hpp"
#include "Widgets/SwitchButton.hpp"
#include "Widgets/SwitchButton.hpp" // Orca: SwitchBoard (m_nozzle_btn_panel); not transitively available here
#include "Widgets/PopupWindow.hpp"
#include <wx/simplebook.h>
#include <wx/hashmap.h>
#include "slic3r/GUI/DeviceCore/DevUtil.h"
#include <optional>
#define MAPPING_ITEM_INVALID_REMAIN -1
namespace Slic3r {
// Previous definitions
namespace Slic3r
{
class DevNozzleRack;
namespace GUI { class wgtDeviceNozzleRackSelect; }
}
namespace GUI
{
class wgtDeviceNozzleRackSelect;
class wgtMsgPanel;
};
};
namespace Slic3r { namespace GUI {
namespace Slic3r {
namespace GUI {
#define AMS_TOTAL_COUNT 4
@@ -64,7 +69,8 @@ enum TrayType {
enum ShowType {
LEFT, // only show left ams and left ext
RIGHT, //only show right ams and right ext
LEFT_AND_RIGHT //show left and right ams at the same time
LEFT_AND_RIGHT, //show left and right ams at the same time
LEFT_AND_RIGHT_DYNAMIC //show all left and right at one panel when use_dynamic_switch
};
struct TrayData
@@ -85,26 +91,59 @@ struct TrayData
class MaterialItem: public wxPanel
{
protected:
int m_text_pos_x = 0;
int m_text_pos_y = -1;
bool m_dropdown_allow_painted = true;
wxString m_mapping_text;
public:
MaterialItem(wxWindow *parent, wxColour mcolour, wxString mname);
// Orca: filament_id defaults to empty so the existing 3-arg callers keep compiling;
// m_filament_id is otherwise unused here.
MaterialItem(wxWindow *parent, wxColour mcolour, wxString mname, std::string filament_id = std::string());
~MaterialItem();
wxPanel* m_main_panel;
void allow_paint_dropdown(bool flag);
void set_ams_text(const wxString& txt);
void set_ams_info(wxColour col, wxString txt,
int ctype = 0, std::vector<wxColour> cols = std::vector<wxColour>(),
bool record_back_info = false);
void set_material_cols(int ctype, const std::vector<wxColour>& cols);
void reset_ams_info();
virtual void reset_valid_info();
void set_nozzle_info(const wxString& mapped_nozzle_str);
void disable();
void enable();
void on_normal();
void on_selected();
void on_warning();
bool is_selected() const { return m_selected;}
bool is_warning() const { return m_warning;}
void msw_rescale();
protected:
void messure_size();
public:
std::string m_filament_id;
wxColour m_material_coloul;
int m_material_ctype = 0;
std::vector<wxColour> m_material_cols;
wxString m_material_name;
wxString m_mapped_nozzle_str;
//info
wxColour m_ams_coloul;
wxString m_ams_name;
// Physical nozzle(s) the print-dispatch mapping assigned to this filament (e.g. "R1", "L R").
// Empty on printers without a nozzle rack or filament switcher; when set, the card grows a row.
wxString m_mapped_nozzle_str;
int m_ams_ctype = 0;
std::vector<wxColour> m_ams_cols = std::vector<wxColour>();
//reset
wxColour m_back_ams_coloul;
wxString m_back_ams_name;
@@ -124,32 +163,17 @@ public:
bool m_match {true};
bool m_enable {true};
void msw_rescale();
void allow_paint_dropdown(bool flag);
void set_ams_info(wxColour col, wxString txt, int ctype=0, std::vector<wxColour> cols= std::vector<wxColour>(),bool record_back_info = false);
void reset_ams_info();
// Set the mapped-nozzle label ("R1", "L R", ...); grows/shrinks the card as the row appears/clears.
void set_nozzle_info(const wxString& mapped_nozzle_str);
// Size the card: base swatch height, plus one row when a nozzle label is present.
void messure_size();
void disable();
void enable();
void on_normal();
void on_selected();
void on_warning();
void paintEvent(wxPaintEvent &evt);
virtual void render(wxDC &dc);
void match(bool mat);
virtual void doRender(wxDC &dc);
virtual void reset_valid_info();
};
class MaterialSyncItem : public MaterialItem
{
public:
MaterialSyncItem(wxWindow *parent, wxColour mcolour, wxString mname);
// Orca: filament_id defaults to empty for existing 3-arg callers (see MaterialItem).
MaterialSyncItem(wxWindow *parent, wxColour mcolour, wxString mname, std::string filament_id = std::string());
~MaterialSyncItem();
int get_real_offset();
void render(wxDC &dc) override;
@@ -181,7 +205,7 @@ public:
public:
void update_data(TrayData data);
void send_event(int fliament_id);
void set_data(const wxString& tag_name, wxColour colour, wxString name, bool remain_detect, TrayData data, bool unmatch = false);
void set_data(const wxString& tag_name, wxColour colour, wxString name, bool remain_detect, TrayData data, bool unmatch = false, std::optional<wxString> tooltip_opt = std::nullopt);
void set_checked(bool checked);
void set_tray_index(wxString t_index) { m_tray_index = t_index; };
@@ -222,13 +246,38 @@ protected:
void doRender(wxDC& dc);
};
class DevIconLabel : public wxPanel
{
public:
DevIconLabel(wxWindow* parent, const wxString& icon, const wxString& label);
public:
void SetAllBackgroundColor(const wxColour& color);
Label* GetLabelItem() const { return m_label; }
void SetLabel(const wxString& label);
void SetIcon(const wxString& icon);
void Rescale();
private:
void CreateGui();
private:
Label* m_label{ nullptr };
wxString m_icon_str;
wxStaticBitmap* m_icon{ nullptr };
};
class AmsMapingPopup : public PopupWindow
{
bool m_use_in_sync_dialog = false;
bool m_ams_remain_detect_flag = false;
bool m_ext_mapping_filatype_check = true;
wxStaticText* m_title_text{ nullptr };
wgtDeviceNozzleRackSelect *m_rack_nozzle_select{nullptr};
DevIconLabel* m_flush_warning_panel;
public:
AmsMapingPopup(wxWindow *parent,bool use_in_sync_dialog = false);
~AmsMapingPopup() {};
@@ -244,11 +293,11 @@ public:
bool m_has_unmatch_filament {false};
int m_current_filament_id;
ShowType m_show_type{ShowType::RIGHT};
// Orca: set from update() — true when a Filament Track Switch is installed, which makes the
// external-spool slots un-pickable (false on any printer without a switch, so behavior is unchanged).
bool m_fila_switch_installed{false};
std::string m_tag_material;
wxScrolledWindow *m_scrolled_window{nullptr};
wxBoxSizer *m_sizer_main{nullptr};
wxBoxSizer *m_sizer_main_h{nullptr};
wxBoxSizer *m_sizer_ams_v{nullptr};
wxBoxSizer *m_sizer_ams{nullptr};
wxBoxSizer *m_sizer_ams_left{nullptr};
wxBoxSizer *m_sizer_ams_right{nullptr};
@@ -262,26 +311,27 @@ public:
MappingItem* m_right_extra_slot{nullptr};
wxPanel * m_left_marea_panel{nullptr};
wxPanel * m_right_marea_panel{nullptr};
wxPanel* m_right_marea_panel{ nullptr }; // used as right if both left and right sides shown. used as single panel if only one side shown.
wxPanel * m_left_first_text_panel{nullptr};
wxPanel * m_right_first_text_panel{nullptr};
wgtMsgPanel* m_ams_tips_msg_panel{nullptr};
wxPanel * m_split_line_panel{nullptr};
wxBoxSizer * m_left_split_ams_sizer{nullptr};
wxBoxSizer * m_right_split_ams_sizer{nullptr};
wxBoxSizer * m_right_split_ext_sizer{ nullptr };
Label * m_left_tips{nullptr};
Label * m_right_tips{nullptr};
ScalableButton* m_reset_btn{nullptr};
wxString m_single_tip_text;
wxString m_left_tip_text;
wxString m_right_tip_text;
wxString m_ams_tips_panel_text;
wxBoxSizer* m_sizer_split_ams_left;
wxBoxSizer* m_sizer_split_ams_right;
bool m_mapping_from_multi_machines {false};
// Rack nozzle manual-pick (rack printers only; hidden by default via Show(false)).
wgtDeviceNozzleRackSelect* m_rack_nozzle_select{nullptr};
Label* m_flush_warning_panel{nullptr}; // Orca: uses a plain Label for the flush warning
std::weak_ptr<DevNozzleRack> m_rack;
bool get_use_in_sync_dialog() { return m_use_in_sync_dialog; }
void set_sizer_title(wxBoxSizer *sizer, wxString text);
wxBoxSizer* create_split_sizer(wxWindow* parent, wxString text);
void set_send_win(wxWindow* win) {send_win = win;};
@@ -289,8 +339,6 @@ public:
void set_tag_texture(std::string texture);
void update(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch = false, std::optional<PrintFromType> print_type = std::nullopt);
void update_rack_select(MachineObject* obj, bool use_dynamic_switch, std::optional<PrintFromType> print_type);
void update_flush_waste(MachineObject* obj);
void update_title(MachineObject* obj);
void update_items_check_state(const std::vector<FilamentInfo>& ams_mapping_result);
void update_ams_data_multi_machines();
void add_ams_mapping(std::vector<TrayData> tray_data, bool remain_detect_flag, wxWindow *container, wxBoxSizer *sizer);
@@ -304,15 +352,24 @@ public:
void paintEvent(wxPaintEvent &evt);
void set_parent_item(MaterialItem* item) {m_parent_item = item;};
void set_show_type(ShowType type) { m_show_type = type; };
// Orca: kept as a no-op for SelectMachine — its only caller passes false, which
// equals the update path's default (all slots shown).
void set_only_show_ext_spool(bool /*flag*/) {}
// Orca: dropped by the reference but still consumed by SelectMachine.
std::vector<TrayData> parse_ams_mapping(const std::map<std::string, DevAms*, NumericStrCompare>& amsList);
#ifdef __APPLE__
void on_mouse_move(wxMouseEvent &evt);
wxPopupWindow * m_tip_popup{nullptr};
Label* m_tip_label{nullptr};
#endif
using ResetCallback = std::function<void(const std::string&)>;
void reset_ams_info();
void set_reset_callback(ResetCallback callback);
void show_reset_button();
void set_material_index_str(std::string str) { m_material_index = str; }
const std::string &get_material_index_str() { return m_material_index; }
void set_only_show_ext_spool(bool flag);
public:
void msw_rescale();
@@ -320,11 +377,20 @@ public:
void EnableExtMappingFilaTypeCheck(bool to_check = true) { m_ext_mapping_filatype_check = to_check;} ;
private:
// update
void update_title(MachineObject* obj);
void update_ams_tips(MachineObject* obj);
void update_mapping_items(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch);
// events
void OnNozzleMappingSelected(wxCommandEvent& evt);
void update_flush_waste(MachineObject* obj);
private:
std::weak_ptr<DevNozzleRack> m_rack;
ResetCallback m_reset_callback{nullptr};
std::string m_material_index;
bool m_only_show_ext_spool{false};
};
class AmsMapingTipPopup : public PopupWindow
+708
View File
@@ -0,0 +1,708 @@
/*****************************************************************//**
* \file AmsMappingPopupUpdate.cpp
* \brief do update for AmsMappingPopup
* \note moved from AmsMappingPopup.cpp to here to reduce the code size of AmsMappingPopup.cpp
*
* \author xin.zhang
* \date February 2026
*********************************************************************/
#include "AmsMappingPopup.hpp"
#include "I18N.hpp"
#include "DeviceCore/DevConfigUtil.h"
#include "libslic3r/Utils.hpp"
#include "slic3r/Utils/WxFontUtils.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_Preview.hpp"
#include "MainFrame.hpp"
#include "format.hpp"
#include "Widgets/ProgressDialog.hpp"
#include "Widgets/RoundedRectangle.hpp"
#include "Widgets/StaticBox.hpp"
#include <wx/progdlg.h>
#include <wx/clipbrd.h>
#include <wx/dcgraph.h>
#include <wx/graphics.h>
#include <miniz.h>
#include <algorithm>
#include <optional>
#include "Plater.hpp"
#include "BitmapCache.hpp"
#include "DeviceCore/DevFilaSystem.h"
#include "DeviceCore/DevFilaSwitch.h"
#include "DeviceCore/DevMappingNozzle.h"
#include "DeviceCore/DevNozzleRack.h" // Orca: full type for GetNozzleSystem()->GetNozzleRack()->IsSupported()
#include "DeviceTab/wgtDeviceNozzleSelect.h"
#include "DeviceTab/wgtMsgPanel.h"
namespace Slic3r::GUI {
static void _add_containers(const AmsMapingPopup* win,
std::list<MappingContainer*>& one_slot_containers,
const std::vector<MappingContainer*>& four_slots_containers,
wxBoxSizer* target_sizer)
{
for (auto container : four_slots_containers) { target_sizer->Add(container, 0, wxTOP, win->FromDIP(5)); }
while (!one_slot_containers.empty()) {
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
for (int i = 0; i < 3; i++) {
if (one_slot_containers.empty()) { break; }
sizer->Add(one_slot_containers.front(), 0, wxLEFT, (i == 0) ? 0 : win->FromDIP(5));
one_slot_containers.pop_front();
}
target_sizer->Add(sizer, 0, wxTOP, win->FromDIP(5));
}
}
void AmsMapingPopup::update(MachineObject* obj,
const std::vector<FilamentInfo>& ams_mapping_result,
bool use_dynamic_switch,
std::optional<PrintFromType> print_type)
{
if (!obj) { return; } // Orca: guard before use (REF logs before its null check)
BOOST_LOG_TRIVIAL(info) << "ams_mapping total count " << obj->GetFilaSystem()->GetAmsCount();
for (auto& ams_container : m_amsmapping_container_list) {
ams_container->Destroy();
}
m_amsmapping_container_list.clear();
m_amsmapping_container_sizer_list.clear();
m_mapping_item_list.clear();
/*title*/
update_title(obj);
/*ams mapping*/
update_ams_tips(obj);
update_mapping_items(obj, ams_mapping_result, use_dynamic_switch);
/*rack*/
update_rack_select(obj, use_dynamic_switch, print_type);
update_flush_waste(obj);
if (wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase_dark") {
m_reset_btn->SetName("erase_dark");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase_dark", 14).bmp());
} else if (!wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase") {
m_reset_btn->SetName("erase");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase", 14).bmp());
}
size_t extruder_num = obj->GetExtderSystem()->GetTotalExtderCount();
if (extruder_num == 1) {
m_left_marea_panel->Hide();
m_left_extra_slot->Hide();
m_split_line_panel->Hide();
m_right_marea_panel->Show();
m_right_marea_panel->Enable(true);
m_right_extra_slot->Show();
m_right_extra_slot->Enable(true);
m_right_split_ext_sizer->Show(true);
set_sizer_title(m_right_split_ams_sizer, _L("AMS"));
} else if (extruder_num > 1) {
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
m_left_marea_panel->Show(false);
m_left_extra_slot->Show(false);
m_right_extra_slot->Show(false);
m_left_split_ams_sizer->Show(false);
m_right_split_ams_sizer->Show(false);
m_right_split_ext_sizer->Show(false);
m_split_line_panel->Hide();
m_right_marea_panel->Show();
m_right_marea_panel->Enable(true);
} else {
m_left_marea_panel->Show();
m_left_extra_slot->Show();
m_split_line_panel->Show();
m_right_marea_panel->Show();
m_right_extra_slot->Show();
m_right_split_ext_sizer->Show(true);
m_left_tips->SetLabel(m_left_tip_text);
m_right_tips->SetLabel(m_right_tip_text);
set_sizer_title(m_left_split_ams_sizer, _L("Left AMS"));
set_sizer_title(m_right_split_ams_sizer, _L("Right AMS"));
if (m_show_type == ShowType::LEFT) {
m_left_marea_panel->Enable(true);
m_left_extra_slot->Enable(true);
m_right_marea_panel->Enable(false);
m_right_extra_slot->Enable(false);
if (m_use_in_sync_dialog) {
m_left_tips->SetLabel(m_single_tip_text);
m_right_tips->SetLabel("");
}
} else if (m_show_type == ShowType::RIGHT) {
m_left_marea_panel->Enable(false);
m_left_extra_slot->Enable(false);
m_right_marea_panel->Enable(true);
m_right_extra_slot->Enable(true);
if (m_use_in_sync_dialog) {
// m_right_tips->SetLabel(m_single_tip_text);
m_left_tips->SetLabel("");
}
} else if (m_show_type == ShowType::LEFT_AND_RIGHT) {
m_left_marea_panel->Enable(true);
m_left_extra_slot->Enable(true);
m_right_marea_panel->Enable(true);
m_right_extra_slot->Enable(true);
if (m_use_in_sync_dialog) {
m_left_tips->SetLabel(m_single_tip_text);
m_right_tips->SetLabel("");
}
} else if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
m_left_marea_panel->Show(false);
m_left_extra_slot->Show(false);
m_right_extra_slot->Show(false);
m_left_split_ams_sizer->Show(false);
m_right_split_ams_sizer->Show(false);
m_right_split_ext_sizer->Show(false);
m_split_line_panel->Hide();
m_right_marea_panel->Show();
m_right_marea_panel->Enable(true);
}
}
}
Layout();
Fit();
Refresh();
}
// Orca: the kept fila model has no tray-level ams_type; the owning AMS type is passed in
// (DevAms::GetAmsType() for AMS trays, DevAmsType::EXT_SPOOL for the virtual/ext slots).
static std::optional<TrayData> sGetTrayData(DevAmsTray* tray,
const std::string& ams_id_str,
DevAmsType ams_type)
{
int ams_id = 0;
int tray_id = 0;
try {
ams_id = std::stoi(ams_id_str);
tray_id = atoi(tray->id.c_str());
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":[error] invalid ams_id or tray_id: " << e.what();
return std::nullopt;
}
TrayData td;
td.ams_id = ams_id;
if (ams_type != DevAmsType::EXT_SPOOL) {
td.slot_id = tray_id;
} else {
td.slot_id = 0;
}
if (ams_type == DevAmsType::AMS ||
ams_type == DevAmsType::AMS_LITE ||
ams_type == DevAmsType::N3F) {
td.id = ams_id * 4 + tray_id;
} else if (ams_type == DevAmsType::N3S) {
td.id = ams_id + tray_id;
} else if(ams_type == DevAmsType::EXT_SPOOL){
td.id = tray_id;
}
if (ams_type != EXT_SPOOL && !tray->is_exists) {
td.type = EMPTY;
} else {
if (!tray->is_tray_info_ready()) {
td.type = THIRD;
} else {
td.type = NORMAL;
td.remain = tray->remain;
td.colour = DevAmsTray::decode_color(tray->color);
td.name = tray->get_display_filament_type();
td.filament_type = tray->get_filament_type();
td.ctype = tray->ctype;
for (const auto& col : tray->cols) {
td.material_cols.push_back(DevAmsTray::decode_color(col));
}
}
}
return td;
}
void AmsMapingPopup::update_mapping_items(MachineObject* obj, const std::vector<FilamentInfo>& ams_mapping_result, bool use_dynamic_switch)
{
std::list<MappingContainer*> left_one_slot_containers;
std::list<MappingContainer*> right_one_slot_containers;
std::vector<MappingContainer*> left_four_slots_containers;
std::vector<MappingContainer*> right_four_slot_containers;
const auto& ams_list = obj->GetFilaSystem()->GetAmsList();
for (auto ams_iter = ams_list.begin(); ams_iter != ams_list.end(); ams_iter++) {
const auto& extruder_set = ams_iter->second->GetBindedExtruderSet();
if (extruder_set.size() <= 0) {
continue;
}
wxPanel* target_panel = nullptr;
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
target_panel = m_right_marea_panel;
} else {
int extruder_id = *extruder_set.begin();
if (extruder_id == MAIN_EXTRUDER_ID) {
target_panel = m_right_marea_panel;
} else if (extruder_id == DEPUTY_EXTRUDER_ID) {
target_panel = m_left_marea_panel;
}
}
if (!target_panel) {
continue;
}
auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
auto ams_mapping_item_container = new MappingContainer(target_panel, ams_iter->second->GetDisplayName(), ams_iter->second->GetSlotCount());
ams_mapping_item_container->SetName(target_panel->GetName());
ams_mapping_item_container->SetSizer(sizer_mapping_list);
ams_mapping_item_container->Layout();
BOOST_LOG_TRIVIAL(trace) << "ams_mapping ams id " << ams_iter->first.c_str();
DevAms* ams_group = ams_iter->second;
auto ams_type = ams_group->GetAmsType();
std::vector<TrayData> tray_datas;
std::map<std::string, DevAmsTray*>::const_iterator tray_iter;
for (tray_iter = ams_group->GetTrays().cbegin(); tray_iter != ams_group->GetTrays().cend(); tray_iter++) {
const auto& td_opt = sGetTrayData(tray_iter->second, ams_group->GetAmsId(), ams_type);
if (td_opt.has_value()) {
tray_datas.push_back(td_opt.value());
}
}
ams_mapping_item_container->Show();
add_ams_mapping(tray_datas, obj->GetFilaSystem()->IsDetectRemainEnabled(), ams_mapping_item_container, sizer_mapping_list);
m_amsmapping_container_sizer_list.push_back(sizer_mapping_list);
m_amsmapping_container_list.push_back(ams_mapping_item_container);
if (target_panel == m_right_marea_panel) {
if (ams_mapping_item_container->get_slots_num() == 1) {
right_one_slot_containers.push_back(ams_mapping_item_container);
} else {
right_four_slot_containers.push_back(ams_mapping_item_container);
}
} else if (target_panel == m_left_marea_panel) {
if (ams_mapping_item_container->get_slots_num() == 1) {
left_one_slot_containers.push_back(ams_mapping_item_container);
} else {
left_four_slots_containers.push_back(ams_mapping_item_container);
}
}
}
for (int i = obj->vt_slot.size() - 1; i >= 0; i--) {
DevAmsTray* tray_data = &obj->vt_slot[i];
const auto& td_opt = sGetTrayData(tray_data, tray_data->id, DevAmsType::EXT_SPOOL);
if (!td_opt.has_value()) {
continue;
}
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
const auto& shown_name = td_opt->ams_id == VIRTUAL_TRAY_MAIN_ID ? "Ext-R" : "Ext-L";
auto ams_mapping_item_container = new MappingContainer(m_right_marea_panel, shown_name, 1);
ams_mapping_item_container->SetName(m_right_marea_panel->GetName());
ams_mapping_item_container->SetSizer(sizer_mapping_list);
ams_mapping_item_container->Layout();
std::vector<TrayData> tray_datas;
tray_datas.push_back(td_opt.value());
add_ams_mapping(tray_datas, false, ams_mapping_item_container, sizer_mapping_list);
m_amsmapping_container_sizer_list.push_back(sizer_mapping_list);
m_amsmapping_container_list.push_back(ams_mapping_item_container);
right_one_slot_containers.push_back(ams_mapping_item_container);
} else {
if (td_opt->ams_id == VIRTUAL_TRAY_MAIN_ID) {
m_right_extra_slot->send_win = send_win;
add_ext_ams_mapping(td_opt.value(), m_right_extra_slot);
} else if (td_opt->ams_id == VIRTUAL_TRAY_DEPUTY_ID) {
m_left_extra_slot->send_win = send_win;
add_ext_ams_mapping(td_opt.value(), m_left_extra_slot);
}
}
}
_add_containers(this, left_one_slot_containers, left_four_slots_containers, m_sizer_ams_basket_left);
_add_containers(this, right_one_slot_containers, right_four_slot_containers, m_sizer_ams_basket_right);
if (m_show_type != ShowType::LEFT_AND_RIGHT_DYNAMIC){
m_left_split_ams_sizer->Show(!left_one_slot_containers.empty() || !left_four_slots_containers.empty());
m_right_split_ams_sizer->Show(!right_one_slot_containers.empty() || !right_four_slot_containers.empty());
} else {
m_left_split_ams_sizer->Show(false);
m_right_split_ams_sizer->Show(false);
}
update_items_check_state(ams_mapping_result);
}
void AmsMapingPopup::update_ams_data_multi_machines()
{
m_mapping_from_multi_machines = true;
std::vector<TrayData> tray_datas;
for (int i = 0; i < 4; ++i) {
TrayData td;
td.id = i;
td.type = EMPTY;
td.colour = wxColour(166, 169, 170);
td.name = "";
td.filament_type = "";
td.ctype = 0;
tray_datas.push_back(td);
}
for (auto& ams_container : m_amsmapping_container_list) {
ams_container->Destroy();
}
m_amsmapping_container_list.clear();
m_amsmapping_container_sizer_list.clear();
m_mapping_item_list.clear();
if (wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase_dark") {
m_reset_btn->SetName("erase_dark");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase_dark", 14).bmp());
} else if (!wxGetApp().dark_mode() && m_reset_btn->GetName() != "erase") {
m_reset_btn->SetName("erase");
m_reset_btn->SetBitmap(ScalableBitmap(m_right_first_text_panel, "erase", 14).bmp());
}
size_t nozzle_nums = 1;
m_show_type = ShowType::RIGHT;
m_left_marea_panel->Hide();
m_left_extra_slot->Hide();
// m_left_marea_panel->Show();
m_right_marea_panel->Show();
set_sizer_title(m_right_split_ams_sizer, _L("AMS"));
// m_right_tips->SetLabel(m_single_tip_text);
m_right_extra_slot->Hide();
m_left_extra_slot->Hide();
/*ams*/
bool has_left_ams = false, has_right_ams = false;
std::list<MappingContainer*> left_one_slot_containers;
std::list<MappingContainer*> right_one_slot_containers;
std::vector<MappingContainer*> left_four_slots_containers;
std::vector<MappingContainer*> right_four_slot_containers;
for (int i = 0; i < 1; i++) {
int ams_indx = 0;
int ams_type = 1;
int nozzle_id = 0;
if (ams_type >= 1 || ams_type <= 3) { // 1:ams 2:ams-lite 3:n3f
auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
auto ams_mapping_item_container = new MappingContainer(nozzle_id == 0 ? m_right_marea_panel : m_left_marea_panel, "AMS-1", 4);
ams_mapping_item_container->SetName(nozzle_id == 0 ? m_right_marea_panel->GetName() : m_left_marea_panel->GetName());
ams_mapping_item_container->SetSizer(sizer_mapping_list);
ams_mapping_item_container->Layout();
m_has_unmatch_filament = false;
ams_mapping_item_container->Show();
add_ams_mapping(tray_datas, false, ams_mapping_item_container, sizer_mapping_list);
m_amsmapping_container_sizer_list.push_back(sizer_mapping_list);
m_amsmapping_container_list.push_back(ams_mapping_item_container);
if (nozzle_id == 0) {
has_right_ams = true;
if (ams_mapping_item_container->get_slots_num() == 1) {
right_one_slot_containers.push_back(ams_mapping_item_container);
} else {
right_four_slot_containers.push_back(ams_mapping_item_container);
}
} else if (nozzle_id == 1) {
has_left_ams = true;
if (ams_mapping_item_container->get_slots_num() == 1) {
left_one_slot_containers.push_back(ams_mapping_item_container);
} else {
left_four_slots_containers.push_back(ams_mapping_item_container);
}
}
} else if (ams_type == 4) { // 4:n3s
}
}
_add_containers(this, left_one_slot_containers, left_four_slots_containers, m_sizer_ams_basket_left);
_add_containers(this, right_one_slot_containers, right_four_slot_containers, m_sizer_ams_basket_right);
m_left_split_ams_sizer->Show(has_left_ams);
m_right_split_ams_sizer->Show(has_right_ams);
//update_items_check_state(ams_mapping_result);
Layout();
Fit();
}
void AmsMapingPopup::update_title(MachineObject* obj)
{
const auto& full_config = wxGetApp().preset_bundle->full_config();
size_t nozzle_nums = full_config.option<ConfigOptionFloatsNullable>("nozzle_diameter")->values.size();
if (nozzle_nums > 1) {
m_split_line_panel->Show();
if (m_show_type == ShowType::LEFT) {
wxString nozzle_name = obj ? _L(DevPrinterConfigUtil::get_toolhead_display_name(
obj->printer_type, DEPUTY_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase)) : _L("left nozzle");
m_title_text->SetLabelText(wxString::Format(_L("Please select the filament installed on the %s."), nozzle_name));
return;
} else if (m_show_type == ShowType::RIGHT) {
wxString nozzle_name = obj ? _L(DevPrinterConfigUtil::get_toolhead_display_name(
obj->printer_type, MAIN_EXTRUDER_ID, ToolHeadComponent::Nozzle, ToolHeadNameCase::LowerCase)) : _L("right nozzle");
m_title_text->SetLabelText(wxString::Format(_L("Please select the filament installed on the %s."), nozzle_name));
return;
} else if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
m_title_text->SetLabelText(_L("Please select the filament."));
return;
}
} else if (nozzle_nums == 1) {
m_split_line_panel->Hide();
}
m_title_text->SetLabelText(_L("Nozzle"));
}
void AmsMapingPopup::update_ams_tips(MachineObject* obj)
{
const auto& full_config = wxGetApp().preset_bundle->full_config();
size_t nozzle_nums = full_config.option<ConfigOptionFloatsNullable>("nozzle_diameter")->values.size();
if (m_ams_tips_msg_panel) {
m_ams_tips_msg_panel->Clear();
if (nozzle_nums == 2 && m_show_type != ShowType::LEFT_AND_RIGHT) {
m_ams_tips_msg_panel->AddMessage(_L("To learn about the filaments matching rules."),
"#FF6F00", ""); // Orca: dropped vendor help link
}
if (obj && obj->GetFilaSwitch()->IsInstalled()) {
const auto& msg = _L("External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it.");
m_ams_tips_msg_panel->AddMessage(msg, "#FF6F00", "");
}
m_ams_tips_msg_panel->Layout();
m_ams_tips_msg_panel->Fit();
m_ams_tips_msg_panel->Show(m_ams_tips_msg_panel->GetMessageCount() > 0);
}
}
void AmsMapingPopup::update_rack_select(MachineObject* obj, bool use_dynamic_switch, std::optional<PrintFromType> print_from_type)
{
// Orca: MachineObject has no GetNozzleRack() convenience; route through the nozzle system instead.
m_rack = obj ? obj->GetNozzleSystem()->GetNozzleRack() : nullptr;
bool show_rack_select_area = false;
if (!m_mapping_from_multi_machines && !m_use_in_sync_dialog &&
obj && obj->GetNozzleSystem()->GetNozzleRack()->IsSupported() &&
print_from_type.has_value() && print_from_type.value() == PrintFromType::FROM_NORMAL) {
const auto& nozzle_pos_vec = obj->get_nozzle_mapping_result()->GetMappedNozzlePosVecByFilaId(m_current_filament_id);
m_rack_nozzle_select->UpdatSelectedNozzles(obj->GetNozzleSystem()->GetNozzleRack(), nozzle_pos_vec, use_dynamic_switch, print_from_type);
show_rack_select_area = true;
}
if (show_rack_select_area != m_rack_nozzle_select->IsShown()) {
m_right_tip_text = show_rack_select_area ? _L("Select Filament && Hotends") : _L("Select Filament");
m_right_tips->SetLabel(m_right_tip_text);
m_rack_nozzle_select->Show(show_rack_select_area);
Layout();
Fit();
}
}
void AmsMapingPopup::update_items_check_state(const std::vector<FilamentInfo>& ams_mapping_result)
{
/*update check states*/
if (m_parent_item) {
auto update_item_check_state = [&ams_mapping_result, this](MappingItem* item) {
if (item) {
for (const auto& mapping_res : ams_mapping_result) {
if (mapping_res.id == this->m_current_filament_id) {
if (mapping_res.ams_id == std::to_string(item->m_ams_id) &&
mapping_res.slot_id == std::to_string(item->m_slot_id)) {
item->set_checked(true);
} else {
item->set_checked(false);
}
return;
}
}
item->set_checked(false);
}
};
update_item_check_state(m_left_extra_slot);
update_item_check_state(m_right_extra_slot);
for (auto mapping_item : m_mapping_item_list) {
update_item_check_state(mapping_item);
}
}
}
void AmsMapingPopup::add_ams_mapping(std::vector<TrayData> tray_data,
bool remain_detect_flag,
wxWindow* container,
wxBoxSizer* sizer)
{
sizer->Add(0, 0, 0, wxLEFT, FromDIP(6));
for (auto i = 0; i < tray_data.size(); i++) {
// set button
MappingItem* m_mapping_item = new MappingItem(container);
m_mapping_item->send_win = send_win;
m_mapping_item->m_ams_id = tray_data[i].ams_id;
m_mapping_item->m_slot_id = tray_data[i].slot_id;
// Orca: transition_tridid here has no total-extruder-count overload; label the virtual (ext)
// slots Ext-R / Ext-L inline in the two-nozzle left+right views (matches that overload's result).
if ((m_show_type == ShowType::LEFT_AND_RIGHT || m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC)
&& (tray_data[i].id == VIRTUAL_TRAY_MAIN_ID || tray_data[i].id == VIRTUAL_TRAY_DEPUTY_ID)) {
m_mapping_item->set_tray_index(tray_data[i].id == VIRTUAL_TRAY_MAIN_ID ? wxString("Ext-R") : wxString("Ext-L"));
} else {
m_mapping_item->set_tray_index(wxGetApp().transition_tridid(tray_data[i].id));
}
m_mapping_item->SetSize(wxSize(FromDIP(48), FromDIP(60)));
m_mapping_item->SetMinSize(wxSize(FromDIP(48), FromDIP(60)));
m_mapping_item->SetMaxSize(wxSize(FromDIP(48), FromDIP(60)));
// traversal if can pick the item or not
bool can_pick_the_item = true;
std::optional<wxString> item_tooltip_msg;
// check filament area
if (m_use_in_sync_dialog) {
if (can_pick_the_item) {
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
can_pick_the_item = !devPrinterUtil::IsVirtualSlot(m_mapping_item->m_ams_id);
if (!can_pick_the_item) {
item_tooltip_msg = _L(
"External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it.");
}
}
}
}
else {
if (can_pick_the_item) {
// LEFT_AND_RIGHT — the fallback when a filament has no entry in the
// nozzle map — keeps both panels pickable.
auto parent = container->GetParent();
if (parent == m_left_marea_panel) {
can_pick_the_item = (m_show_type == ShowType::LEFT || m_show_type == ShowType::LEFT_AND_RIGHT);
} else if (parent == m_right_marea_panel) {
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
can_pick_the_item = !devPrinterUtil::IsVirtualSlot(m_mapping_item->m_ams_id);
if (!can_pick_the_item) {
item_tooltip_msg = _L(
"External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it.");
}
} else if (m_show_type != ShowType::RIGHT && m_show_type != ShowType::LEFT_AND_RIGHT) {
can_pick_the_item = false;
}
}
}
}
// check filament type
if (can_pick_the_item) {
if (tray_data[i].type == NORMAL && !is_match_material(tray_data[i].filament_type)){
can_pick_the_item = false;
} else if(tray_data[i].type == EMPTY){
// Mapping to an empty slot is only allowed from the multi-machine send page,
// which maps onto EMPTY placeholder trays.
can_pick_the_item = m_mapping_from_multi_machines;
}
}
wxColour display_color;
if (can_pick_the_item) {
display_color = tray_data[i].type == NORMAL ? tray_data[i].colour : wxColour(0xCE, 0xCE, 0xCE);
} else {
display_color = wxColour(0xEE, 0xEE, 0xEE);
}
wxString display_name;
if (tray_data[i].type == THIRD) {
display_name = "?";
} else if (tray_data[i].type == EMPTY) {
display_name = "-";
} else {
display_name = tray_data[i].name;
}
m_mapping_item->set_data(m_tag_material, display_color, display_name, remain_detect_flag, tray_data[i], !can_pick_the_item, item_tooltip_msg);
m_mapping_item->Bind(wxEVT_LEFT_DOWN, [this, can_pick_the_item, m_mapping_item](wxMouseEvent& e) {
if (can_pick_the_item) {
m_mapping_item->send_event(m_current_filament_id);
Dismiss();
}
});
sizer->Add(0, 0, 0, wxRIGHT, FromDIP(6));
sizer->Add(m_mapping_item, 0, wxTOP, FromDIP(1));
m_mapping_item_list.push_back(m_mapping_item);
}
}
void AmsMapingPopup::add_ext_ams_mapping(TrayData tray_data, MappingItem* item)
{
#ifdef __APPLE__
m_mapping_item_list.push_back(item);
#endif
// set button
if (tray_data.type == NORMAL) {
if (is_match_material(tray_data.filament_type)) {
item->set_data(m_tag_material, tray_data.colour, tray_data.name, false, tray_data);
} else {
item->set_data(m_tag_material, m_ext_mapping_filatype_check ? wxColour(0xEE, 0xEE, 0xEE) : tray_data.colour, tray_data.name, false, tray_data, true);
m_has_unmatch_filament = true;
}
item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, item](wxMouseEvent& e) {
if (!item->GetParent() || !item->GetParent()->IsEnabled()) return;
if (m_ext_mapping_filatype_check && !is_match_material(tray_data.filament_type)) return;
item->send_event(m_current_filament_id);
Dismiss();
});
}
// temp
if (tray_data.type == EMPTY) {
item->set_data(m_tag_material, wxColour(0xCE, 0xCE, 0xCE), "-", false, tray_data);
item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, item](wxMouseEvent& e) {
if (!item->GetParent() || !item->GetParent()->IsEnabled()) return;
item->send_event(m_current_filament_id);
Dismiss();
});
}
// third party
if (tray_data.type == THIRD) {
item->set_data(m_tag_material, tray_data.colour, "?", false, tray_data);
//item->set_data(wxColour(0xCE, 0xCE, 0xCE), "?", tray_data);
item->Bind(wxEVT_LEFT_DOWN, [this, tray_data, item](wxMouseEvent& e) {
if (!item->GetParent() || !item->GetParent()->IsEnabled()) return;
item->send_event(m_current_filament_id);
Dismiss();
});
}
item->set_tray_index("Ext");
}
} // namespace Slic3r::GUI
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -813,7 +813,7 @@ PingCodeBindDialog::~PingCodeBindDialog() {
m_simplebook->SetSelection(0);
auto m_bind_job = std::make_unique<BindJob>(
m_machine_info->get_dev_id(), m_machine_info->get_dev_ip(), m_machine_info->bind_sec_link, m_machine_info->bind_ssdp_version);
m_machine_info->get_dev_id(), m_machine_info->get_dev_ip(), m_machine_info->get_show_printer_type(), m_machine_info->bind_sec_link, m_machine_info->bind_ssdp_version);
if (m_machine_info && (m_machine_info->get_printer_series() == PrinterSeries::SERIES_X1)) {
m_bind_job->set_improved(false);
+3 -3
View File
@@ -102,7 +102,7 @@ CameraPopup::CameraPopup(wxWindow *parent)
top_sizer->Add(0, 0, wxALL, 0);
}
// custom IP camera
// Orca: custom IP camera source — lets the user point Live Video at any camera URL (Orca feature; not in the reference)
m_custom_camera_input_confirm = new Button(m_panel, _L("Enable"));
m_custom_camera_input_confirm->SetBackgroundColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetBorderColor(wxColour(38, 166, 154));
@@ -132,7 +132,7 @@ CameraPopup::CameraPopup(wxWindow *parent)
top_sizer->Add(m_custom_camera_input_confirm, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/virtual-camera", L"en");
auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed)
auto text = _L("Show \"Live Video\" guide page.");
wxBoxSizer* link_sizer = new wxBoxSizer(wxVERTICAL);
@@ -479,7 +479,7 @@ void CameraPopup::OnLeftUp(wxMouseEvent &event)
//hyper link
auto h_rect = vcamera_guide_link->ClientToScreen(wxPoint(0, 0));
if (mouse_pos.x > h_rect.x && mouse_pos.y > h_rect.y && mouse_pos.x < (h_rect.x + vcamera_guide_link->GetSize().x) && mouse_pos.y < (h_rect.y + vcamera_guide_link->GetSize().y)) {
auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/virtual-camera", L"en");
auto url = wxString(L"https://www.orcaslicer.com/wiki/"); // Orca: neutral wiki link (vendor URL removed)
wxLaunchDefaultBrowser(url);
}
}
+13
View File
@@ -60,6 +60,19 @@ list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceCore/DevUtil.cpp
GUI/DeviceCore/DevUtilBackend.h
GUI/DeviceCore/DevUtilBackend.cpp
GUI/DeviceCore/DevAxis.h
GUI/DeviceCore/DevAxis.cpp
GUI/DeviceCore/DevAxisCtrl.cpp
GUI/DeviceCore/DevCalib.h
GUI/DeviceCore/DevCalib.cpp
GUI/DeviceCore/DevChamber.h
GUI/DeviceCore/DevChamber.cpp
GUI/DeviceCore/DevChamberCtrl.cpp
GUI/DeviceCore/DevStatus.h
GUI/DeviceCore/DevStatus.cpp
GUI/DeviceCore/DevUpgrade.h
GUI/DeviceCore/DevUpgrade.cpp
GUI/DeviceCore/DevUpgradeCtrl.cpp
)
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)
+25
View File
@@ -0,0 +1,25 @@
#include "DevAxis.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r
{
void DevAxis::ParseAxis(const json& print_json)
{
DevJsonValParser::ParseVal(print_json, "home_flag", m_home_flag);
if (print_json.contains("fun")) {
const std::string& fun = print_json["fun"].get<std::string>();
m_is_support_mqtt_homing = DevUtil::get_flag_bits(fun, 32);
m_is_support_mqtt_axis_ctrl = DevUtil::get_flag_bits(fun, 38);
}
}
bool DevAxis::IsArchCoreXY() const
{
return m_owner->get_printer_arch() == PrinterArch::ARCH_CORE_XY;
}
} // namespace Slic3r
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
namespace Slic3r {
class MachineObject;
class DevAxis
{
public:
static std::shared_ptr<DevAxis> Create(MachineObject *obj) { return std::shared_ptr<DevAxis>( new DevAxis(obj)); }
virtual ~DevAxis() = default;
public:
bool IsAxisAtHomeX() const { return m_home_flag == 0 ? true : (m_home_flag & 1) == 1; }
bool IsAxisAtHomeY() const { return m_home_flag == 0 ? true : ((m_home_flag >> 1) & 1) == 1; }
bool IsAxisAtHomeZ() const { return m_home_flag == 0 ? true : ((m_home_flag >> 2) & 1) == 1; }
bool IsArchCoreXY() const;
public:
void ParseAxis(const json &print_json);
int Ctrl_GoHome();
int Ctrl_Axis(std::string axis, double unit = 1.0f, double input_val = 1.0f, int speed = 3000); // xyz e
protected:
DevAxis(MachineObject *obj) noexcept : m_owner(obj) {}
private:
MachineObject *m_owner = nullptr;
int m_home_flag = 0; // bit0:X, bit1:Y, bit2:Z
bool m_is_support_mqtt_axis_ctrl = false;
bool m_is_support_mqtt_homing = false;
};
} // namespace Slic3r
+60
View File
@@ -0,0 +1,60 @@
#include "DevAxis.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
namespace Slic3r
{
int DevAxis::Ctrl_GoHome()
{
if (m_is_support_mqtt_homing) {
json j;
j["print"]["command"] = "back_to_center";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return m_owner->publish_json(j);
}
// gcode command
return m_owner->is_in_printing() ? m_owner->publish_gcode("G28 X\n") : m_owner->publish_gcode("G28 \n");
}
int DevAxis::Ctrl_Axis(std::string axis, double unit, double input_val, int speed)
{
if (m_is_support_mqtt_axis_ctrl) {
int dir = input_val > 0 ? 1 : -1;
if (!IsArchCoreXY()) {
if (axis.compare("Y") == 0 || axis.compare("Z") == 0) {
dir = -dir;
}
}
json j;
j["print"]["command"] = "xyz_ctrl";
j["print"]["axis"] = axis;
j["print"]["dir"] = dir;
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return m_owner->publish_json(j);
}
double value = input_val;
if (!IsArchCoreXY()) {
if (axis.compare("Y") == 0 || axis.compare("Z") == 0) {
value = -1.0 * input_val;
}
}
char cmd[256];
if (axis.compare("X") == 0 || axis.compare("Y") == 0 || axis.compare("Z") == 0) {
sprintf(cmd, "M211 S \nM211 X1 Y1 Z1\nM1002 push_ref_mode\nG91 \nG1 %s%0.1f F%d\nM1002 pop_ref_mode\nM211 R\n", axis.c_str(), value * unit, speed);
} else if (axis.compare("E") == 0) {
sprintf(cmd, "M83 \nG0 %s%0.1f F%d\n", axis.c_str(), value * unit, speed);
} else {
return -1;
}
// Orca: strip analytics telemetry (matches MachineObject::command_axis_control)
return m_owner->publish_gcode(cmd);
}
} // namespace Slic3r
+337
View File
@@ -0,0 +1,337 @@
#include <boost/log/trivial.hpp>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/UserNotification.hpp"
#include "libslic3r/PrintConfig.hpp"
#include <wx/dir.h>
#include "fast_float/fast_float.h"
#include "DevCalib.h"
#include "DevDefs.h"
#include "DevFilaSystem.h"
#include "DevConfig.h"
namespace Slic3r {
static float string_to_float(const std::string &str_value)
{
float value = 0.0;
fast_float::from_chars(str_value.c_str(), str_value.c_str() + str_value.size(), value);
return value;
}
static NozzleVolumeType convert_to_nozzle_type(const std::string &str)
{
if (str.size() < 8) {
assert(false && "invalid nozzle info");
return NozzleVolumeType::nvtStandard;
}
if (str[1] == 'S')
return NozzleVolumeType::nvtStandard;
else if (str[1] == 'H')
return NozzleVolumeType::nvtHighFlow;
else if (str[1] == 'U')
return NozzleVolumeType::nvtTPUHighFlow;
// Orca: no nvtE3DHighFlow in Orca's NozzleVolumeType; map 'B' to Standard
else
return NozzleVolumeType::nvtStandard;
}
static float get_number_flexible(const json& j, const std::string& key, float def = 0.0f)
{
if (!j.contains(key)) return def;
const auto& v = j[key];
if (v.is_number_float()) return v.get<float>();
if (v.is_number_integer()) return static_cast<float>(v.get<int>());
if (v.is_string()) return string_to_float(v.get<std::string>());
return def;
}
void from_json(const json& j, PACalibResult& cali) {
cali.extruder_id = j.value("extruder_id", 0);
cali.nozzle_volume_type = convert_to_nozzle_type(j.value("nozzle_id", "HS00-0.4"));
cali.tray_id = j.value("tray_id",0);
cali.ams_id = j.value("ams_id",0);
cali.slot_id = j.value("slot_id",0);
cali.cali_idx = j.value("cali_idx",-1);
cali.nozzle_pos_id = j.value("nozzle_pos",-1);
cali.nozzle_diameter = get_number_flexible(j, "nozzle_diameter", 0.4f);
cali.nozzle_sn = j.value("nozzle_sn","");
cali.filament_id = j.value("filament_id","");
cali.setting_id = j.value("setting_id","");
cali.name = j.value("name","");
cali.k_value = get_number_flexible(j, "k_value", 0.0f);
cali.n_coef = get_number_flexible(j, "n_coef", 0.0f);
cali.confidence = j.value("confidence", 0);
}
void from_json(const json& j, FlowRatioCalibResult& cali)
{
cali.tray_id = j.value("tray_id", 0);
cali.nozzle_diameter = string_to_float(j.value("nozzle_diameter", ""));
cali.filament_id = j.value("filament_id", "");
cali.setting_id = j.value("setting_id", "");
cali.flow_ratio = string_to_float(j.value("flow_ratio", ""));
cali.confidence = j.value("confidence", 0);
}
void DevCalib::ParseCalibVersion(const json& j, DevCalib* system)
{
if(system) system->m_calib_version = j.value("cali_version", -1);
}
bool DevCalib::IsVersionExpired() const
{
if (m_last_calib_version.has_value())
return m_last_calib_version.value() != m_calib_version;
else
return true;
}
void DevCalib::ParseSupportNewAutoCalib(int flag, DevCalib* system)
{
if(system) system->m_support_new_auto_cali = flag;
}
void DevCalib::RequestPAResult()
{
m_pa_results_status = CalibStatus::REQUEST;
}
void DevCalib::ResetPAResult()
{
m_pa_calib_results.clear();
m_pa_results_status = CalibStatus::IDLE;
}
int DevCalib::RequestPAHistory(const PACalibExtruderInfo &calib_info)
{
m_pa_calib_tab.clear();
m_pa_table_status = CalibStatus::REQUEST;
return GetOwner()->command_get_pa_calibration_tab(calib_info);
}
void DevCalib::ResetPAHistory()
{
m_pa_calib_tab.clear();
m_pa_table_status = CalibStatus::IDLE;
}
void DevCalib::RequestFlowRateResult()
{
m_flow_results_status = CalibStatus::REQUEST;
}
void DevCalib::ResetFlowRateResult()
{
m_flow_ratio_results.clear();
m_flow_results_status = CalibStatus::IDLE;
}
void calib_fail_message(MachineObject* obj, std::string cali_mode, std::string reason){
wxString info;
if (reason == "invalid nozzle_diameter" || reason == "nozzle_diameter is not supported") {
info = _L("This calibration does not support the currently selected nozzle diameter");
} else if (reason == "invalid handle_flowrate_cali param") {
info = _L("Current flowrate cali param is invalid");
} else if (reason == "nozzle_diameter is not matched") {
info = _L("Selected diameter and machine diameter do not match");
} else if (reason == "generate auto filament cali gcode failure") {
info = _L("Failed to generate cali gcode");
} else {
info = wxString(reason);
}
GUI::wxGetApp().push_notification(obj, info, _L("Calibration error"), UserNotificationStyle::UNS_WARNING_CONFIRM);
BOOST_LOG_TRIVIAL(info) << cali_mode << " result fail, reason = " << reason;
}
void DevCalib::ExtrusionCalibSetParse(const json & jj){
int tray_id = jj.value("tray_id", -1);
auto tray_ams_slot_map = GetOwner()->GetFilaSystem()->GetTrayIndexMap();
int ams_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].first : -1;
int slot_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].second : -1;
if(tray_id == VIRTUAL_TRAY_MAIN_ID) {
GetOwner()->vt_slot[MAIN_EXTRUDER_ID].k = jj.value("k_value", GetOwner()->vt_slot[MAIN_EXTRUDER_ID].k);
GetOwner()->vt_slot[MAIN_EXTRUDER_ID].n = jj.value("n_value", GetOwner()->vt_slot[MAIN_EXTRUDER_ID].n);
}else{
auto tray_item = GetOwner()->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
if (tray_item) {
tray_item->k = jj.value("k_value", tray_item->k);
tray_item->n = jj.value("n_coef", tray_item->n);
}
}
GetOwner()->extrusion_cali_set_tray_id = tray_id;
GetOwner()->extrusion_cali_set_hold_start = std::chrono::system_clock::now();
}
/* calib select ack parse */
void DevCalib::ExtrusionCalibSelectParse(const json &jj){
try{
int tray_id = jj.value("tray_id", -1);
auto tray_ams_slot_map = GetOwner()->GetFilaSystem()->GetTrayIndexMap();
int default_ams_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].first : -1;
int default_slot_id = tray_ams_slot_map.find(tray_id) != tray_ams_slot_map.end() ? tray_ams_slot_map[tray_id].second : -1;
int ams_id = jj.value("ams_id", default_ams_id);
int slot_id = jj.value("slot_id", default_slot_id);
BOOST_LOG_TRIVIAL(trace) << "extrusion_cali_sel: illegal ams_id = " << ams_id << "slot_id = " << slot_id;
std::vector<DevAmsTray> &vt_slot = GetOwner()->vt_slot;
if (ams_id == VIRTUAL_TRAY_MAIN_ID && vt_slot.size() > 0) {
vt_slot[MAIN_EXTRUDER_ID].cali_idx = jj.value("cali_idx", vt_slot[MAIN_EXTRUDER_ID].cali_idx);
vt_slot[MAIN_EXTRUDER_ID].set_hold_count();
} else if (ams_id == VIRTUAL_TRAY_DEPUTY_ID && vt_slot.size() > 1) {
vt_slot[DEPUTY_EXTRUDER_ID].cali_idx = jj.value("cali_idx", vt_slot[DEPUTY_EXTRUDER_ID].cali_idx);
vt_slot[DEPUTY_EXTRUDER_ID].set_hold_count();
} else {
auto tray_item = GetOwner()->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
if (tray_item) {
tray_item->cali_idx = jj.value("cali_idx", tray_item->cali_idx);
tray_item->set_hold_count();
}
}
} catch(...){
}
}
void DevCalib::ExtrusionCalibGetTableParse(const json &jj){
if (GetPAHistoryStatus() == CalibStatus::REQUEST ) {
m_pa_table_status = CalibStatus::WAITING;
/* request success */
if (!(jj.contains("result") && jj.contains("reason") && jj["result"].get<std::string>() == "fail")) {
SyncCalibVersion();
m_pa_table_status = CalibStatus::FINISHED;
}
try{
json filaments_json;
if(jj.contains("filaments"))
{
/* fill item->nozzle_diameter with command->nozzle_diameter */
filaments_json = jj["filaments"];
for (auto &f : filaments_json) {
if (!f.contains("nozzle_diameter") && jj.contains("nozzle_diameter")) {
f["nozzle_diameter"] = jj["nozzle_diameter"];
}
}
}
m_pa_calib_tab = filaments_json.get<std::vector<PACalibResult>>();
/* filter invalid pa_calib_tab */
m_pa_calib_tab.erase(std::remove_if(m_pa_calib_tab.begin(), m_pa_calib_tab.end(), [](auto &res) { return res.k_value < 0.0f || res.k_value > 10.0f; }), m_pa_calib_tab.end());
if (m_pa_calib_tab.empty()) { BOOST_LOG_TRIVIAL(info) << "empty pa calib history"; }
}catch(...){
m_pa_calib_tab.clear();
BOOST_LOG_TRIVIAL(error) << "pa calib history missing fields, current json:\n "<< jj.dump();
}
// notify cali history to update
}
}
void DevCalib::ExtrusionCalibGetResultParse(const json &jj)
{
m_pa_results_status = CalibStatus::WAITING;
if (!(jj.contains("result") && jj.contains("reason") && jj["result"].get<std::string>() == "fail" && jj.contains("err_code"))) {
m_pa_results_status = CalibStatus::FINISHED;
}
try {
json filaments_json;
if(jj.contains("filaments"))
{
/* fill item->nozzle_diameter with command->nozzle_diameter */
filaments_json = jj["filaments"];
for (auto &f : filaments_json) {
if (!f.contains("nozzle_diameter") && jj.contains("nozzle_diameter") ) {
f["nozzle_diameter"] = jj["nozzle_diameter"];
}
if (IsSupportNewAutoCali()) {
auto ams_id = f.value("ams_id", 0);
auto slot_id = f.value("slot_id", 0);
if(f.contains("tray_id")){
// Orca: Orca's DevFilaSystem has no GetTrayIdByAmsSlotId; mirror its
// semantics via a reverse lookup over GetTrayIndexMap() (correct for
// non-4-slot AMS layouts, unlike a fixed ams_id*4+slot_id formula).
int mapped_tray_id = -1;
const auto tray_ams_slot_map = GetOwner()->GetFilaSystem()->GetTrayIndexMap();
for (const auto& item : tray_ams_slot_map) {
if (item.second.first == ams_id && item.second.second == slot_id) {
mapped_tray_id = item.first;
break;
}
}
f["tray_id"] = mapped_tray_id;
}
}
}
}
m_pa_calib_results = filaments_json.get<std::vector<PACalibResult>>();
m_pa_calib_results.erase(std::remove_if(m_pa_calib_results.begin(), m_pa_calib_results.end(), [](auto &res) { return res.k_value < 0.0f || res.k_value > 10.0f; }), m_pa_calib_results.end());
if (m_pa_calib_results.empty()) { BOOST_LOG_TRIVIAL(info) << "empty pa calib result"; }
} catch (...) {
m_pa_calib_results.clear();
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "pa calibration results missing fileds, current json: \n"<<jj.dump();
}
}
void DevCalib::FlowrateGetResultParse(const json &jj){
m_flow_results_status = CalibStatus::FINISHED;
m_flow_ratio_results.clear();
if(!jj.contains("filaments")) return;
try {
m_flow_ratio_results = jj["filaments"].get<std::vector<FlowRatioCalibResult>>();
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "flow ratio calibration results missing fileds, current json:\n"<<jj.dump();
}
}
void DevCalib::ParseV1_0(const json &jj, DevCalib *system, bool key_field_only)
{
if(!jj.contains("command")) return;
if (jj["command"].get<std::string>() == "extrusion_cali" || jj["command"].get<std::string>() == "flowrate_cali") {
if (jj.contains("result")) {
if (jj["result"].get<std::string>() == "success") {
} else if (jj["result"].get<std::string>() == "fail") {
std::string cali_mode = jj["command"].get<std::string>();
std::string reason = jj["reason"].get<std::string>();
calib_fail_message(system->GetOwner(), cali_mode, reason);
}
}
} else if (jj["command"].get<std::string>() == "extrusion_cali_set") {
system->ExtrusionCalibSetParse(jj);
} else if (jj["command"].get<std::string>() == "extrusion_cali_sel") {
system->ExtrusionCalibSelectParse(jj);
} else if (jj["command"].get<std::string>() == "extrusion_cali_get") {
system->ExtrusionCalibGetTableParse(jj);
} else if (jj["command"].get<std::string>() == "extrusion_cali_get_result") {
system->ExtrusionCalibGetResultParse(jj);
} else if (jj["command"].get<std::string>() == "flowrate_get_result" && !key_field_only) {
system->FlowrateGetResultParse(jj);
}
}
} // namespace Slic3r
+127
View File
@@ -0,0 +1,127 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include "DevDefs.h"
#include "libslic3r/calib.hpp" // Orca: lowercase filename (Linux case-sensitive)
namespace Slic3r {
class MachineObject;
enum class CalibStatus{
IDLE = 0,
REQUEST,
WAITING,
FINISHED,
};
enum class ManualPaCaliMethod {
PA_LINE = 0,
PA_PATTERN,
};
class DevCalib
{
protected:
bool m_support_new_auto_cali{false};
int m_calib_version {-1};
std::optional<int> m_last_calib_version;
public:
DevCalib(MachineObject *obj) : m_owner(obj){};
MachineObject* GetOwner() const {return m_owner; };
void RequestPAResult();
CalibStatus GetPAResultStatus() const {return m_pa_results_status;}
bool IsPAResultReady() const { return m_pa_results_status == CalibStatus::FINISHED;}
void ResetPAResult();
/* calib history */
int RequestPAHistory(const PACalibExtruderInfo &calib_info);
CalibStatus GetPAHistoryStatus() const {return m_pa_table_status;}
bool IsPAHistoryReady() const { return m_pa_table_status == CalibStatus::FINISHED;}
void ResetPAHistory();
void RequestFlowRateResult();
CalibStatus GetFlowRateResultStatus() const {return m_flow_results_status;}
bool IsFlowRateReady() const { return m_flow_results_status == CalibStatus::FINISHED;}
void ResetFlowRateResult();
int GetCalibVersion() const {return m_calib_version;}
void SyncCalibVersion() { if (IsVersionInited()) { m_last_calib_version = m_calib_version; } }
void ResetCalibVersion() {m_last_calib_version.reset();}
bool IsVersionExpired() const;
bool IsVersionInited() const { return m_calib_version > -1;}
bool IsSupportNewAutoCali() const {return m_support_new_auto_cali;}
public:
void SetStashCalibFinished(bool finished) {m_calib_finished = finished;}
bool GetStashCalibFinished() { return m_calib_finished;}
void SetStashFlowRatio(float ratio) { m_flow_ratio = ratio;}
float GetStashFlowRatio() const {return m_flow_ratio;}
FlowRatioCalibrationType GetFlowRatioCalibType() {return m_flow_ratio_calibration_type;}
void SetFlowRatioCalibType(const FlowRatioCalibrationType &type) { m_flow_ratio_calibration_type = type; }
ManualPaCaliMethod GetManualPaCalibMethod() {return m_manual_pa_cali_method;}
void SetManualPaCalibMethod(const ManualPaCaliMethod& method) { m_manual_pa_cali_method = method;}
NozzleDiameterType GetSelectedNozzleDiameter() {return m_selected_nozzle_diameter;}
void SetSelectedNozzleDiameter(const NozzleDiameterType& diameter) { m_selected_nozzle_diameter = diameter;}
std::vector<CaliPresetInfo> GetSelectedCalibPreset() {return m_selected_calib_preset;}
void ResetSelectedCalibPreset() { m_selected_calib_preset.clear();}
void SetSelectedCalibPreset(const std::vector<CaliPresetInfo>& preset) { m_selected_calib_preset = preset;}
std::vector<PACalibResult> GetPAHistory() const {return m_pa_calib_tab; }
std::vector<PACalibResult> GetPAResult() const {return m_pa_calib_results; }
std::vector<FlowRatioCalibResult> GetFlowRatioResult() const {return m_flow_ratio_results; }
protected:
void ExtrusionCalibSetParse(const json &jj);
void ExtrusionCalibSelectParse(const json &jj);
void ExtrusionCalibGetTableParse(const json &jj);
void ExtrusionCalibGetResultParse(const json &jj);
void FlowrateGetResultParse(const json &jj);
private:
MachineObject* m_owner{nullptr};
std::vector<PACalibResult> m_pa_calib_tab;
std::vector<PACalibResult> m_pa_calib_results;
std::vector<FlowRatioCalibResult> m_flow_ratio_results;
CalibStatus m_pa_results_status{CalibStatus::IDLE};
CalibStatus m_pa_table_status{CalibStatus::IDLE};
CalibStatus m_flow_results_status{CalibStatus::IDLE};
FlowRatioCalibrationType m_flow_ratio_calibration_type{FlowRatioCalibrationType::COMPLETE_CALIBRATION};
ManualPaCaliMethod m_manual_pa_cali_method{ManualPaCaliMethod::PA_LINE};
// calibration page selected info
// 1: record when start calibration in preset page
// 2: reset when start calibration in start page
// 3: save tray_id, filament_id, setting_id, and name, nozzle_dia
// std::vector<CaliPresetInfo> selected_cali_preset;
NozzleDiameterType m_selected_nozzle_diameter{NozzleDiameterType::NONE_DIAMETER_TYPE};
std::vector<CaliPresetInfo> m_selected_calib_preset;
// stash calibrating info
float m_flow_ratio { 0.0 };
bool m_calib_finished{false};
public:
static void ParseCalibVersion(const json& j, DevCalib* system);
static void ParseSupportNewAutoCalib(int flag, DevCalib* system);
static void ParseV1_0(const json& print_json, DevCalib* system, bool key_field_only);
};
} // namespace Slic3r
+50
View File
@@ -0,0 +1,50 @@
#include "DevChamber.h"
#include "DevConfig.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r {
bool DevChamber::HasChamber() const { return m_owner->GetConfig()->HasChamber(); }
bool DevChamber::SupportChamberTempDisplay() const { return m_owner->GetConfig()->SupportChamberTempDisplay();}
bool DevChamber::SupportChamberEdit() const { return m_owner->GetConfig()->SupportChamberEdit(); }
int DevChamber::GetChamberTempEditMin() const { return m_owner->GetConfig()->GetChamberTempEditMin(); }
int DevChamber::GetChamberTempEditMax() const { return m_owner->GetConfig()->GetChamberTempEditMax(); }
int DevChamber::GetChamberTempSwitchHeat() const { return m_owner->GetConfig()->GetChamberTempSwitchHeat(); }
void DevChamber::ParseChamber(const json &print_json)
{
ParseChamberV1_0(print_json);
ParseChamberV2_0(print_json);
}
void DevChamber::ParseChamberV1_0(const json &print_json)
{
DevJsonValParser::ParseVal(print_json, "chamber_temper", m_temp);
DevJsonValParser::ParseVal(print_json, "ctt", m_temp_target);
}
void DevChamber::ParseChamberV2_0(const json &print_json)
{
if (print_json.contains("device")) {
const json &device_jj = print_json["device"];
if (device_jj.contains("ctc")) {
const json &ctc = device_jj["ctc"];
int state = DevUtil::get_flag_bits(ctc["state"].get<int>(), 0, 4);
if (ctc.contains("info")) {
const json &info = ctc["info"];
m_temp = DevUtil::get_flag_bits(info["temp"].get<int>(), 0, 16);
m_temp_target = DevUtil::get_flag_bits(info["temp"].get<int>(), 16, 16);
}
}
}
}
} // namespace Slic3r
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
namespace Slic3r {
class MachineObject;
class DevChamber
{
public:
static std::shared_ptr<DevChamber> Create(MachineObject *obj) { return std::shared_ptr<DevChamber>( new DevChamber(obj)); }
public: // getter
bool HasChamber() const;
bool SupportChamberTempDisplay() const;
bool SupportChamberEdit() const;
int GetChamberTempEditMin() const;
int GetChamberTempEditMax() const;
int GetChamberTempSwitchHeat() const;
float GetChamberTemp() const { return m_temp; };
float GetChamberTempTarget() const { return m_temp_target; };
public:
// setter
void ParseChamber(const json &print_json);
void ParseChamberV1_0(const json& print_json);
void ParseChamberV2_0(const json& print_json);
// control
int CtrlSetChamberTemp(int temp);
protected:
DevChamber(MachineObject *obj) : m_owner(obj) {}
private:
float m_temp = 0.0f;
float m_temp_target = 0.0f;
MachineObject* m_owner = nullptr;
};
} // namespace Slic3r
@@ -0,0 +1,16 @@
#include "DevChamber.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r {
int DevChamber::CtrlSetChamberTemp(int temp)
{
json j;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["command"] = "set_ctt";
j["print"]["ctt_val"] = temp;
return m_owner->publish_json(j, 1);
}
} // namespace Slic3r
+14 -1
View File
@@ -8,17 +8,30 @@ using namespace nlohmann;
namespace Slic3r
{
bool DevConfig::SupportChamberTempDisplay() const
{
if (!m_support_chamber_temp_display.has_value()) {
return m_has_chamber;
}
return m_support_chamber_temp_display.value();
}
void DevConfig::ParseConfig(const json& print_json)
{
ParseChamberConfig(print_json);
ParsePrintOptionsConfig(print_json);
ParseCalibrationConfig(print_json);
}
void DevConfig::ParseChamberConfig(const json& print_json)
{
DevJsonValParser::ParseVal(print_json, "support_chamber", m_has_chamber);
if (print_json.contains("support_chamber_temp_display")) {
m_support_chamber_temp_display = DevJsonValParser::GetVal<bool>(print_json, "support_chamber_temp_display");
}
DevJsonValParser::ParseVal(print_json, "support_chamber_temp_edit", m_support_chamber_edit);
if (m_support_chamber_edit)
{
+4 -1
View File
@@ -3,8 +3,9 @@
#include "slic3r/Utils/json_diff.hpp"
#include <wx/string.h>
#include <limits>
#include <limits>
#include <optional>
namespace Slic3r
{
@@ -22,6 +23,7 @@ public:
public:
// chamber
bool HasChamber() const { return m_has_chamber; }
bool SupportChamberTempDisplay() const;
bool SupportChamberEdit() const { return m_support_chamber_edit; }
int GetChamberTempEditMin() const { return m_chamber_temp_edit_min; }
int GetChamberTempEditMax() const { return m_chamber_temp_edit_max; }
@@ -57,6 +59,7 @@ private:
/*configure vals*/
// chamber
bool m_has_chamber = false; // whether the machine has a chamber
std::optional<bool> m_support_chamber_temp_display;
bool m_support_chamber_edit = false;
int m_chamber_temp_edit_min = 0;
int m_chamber_temp_edit_max = 60;
+99 -13
View File
@@ -1,16 +1,41 @@
#include "DevConfigUtil.h"
#include <algorithm>
#include <cctype>
#include <wx/dir.h>
#include <boost/filesystem.hpp>
#include <boost/filesystem/operations.hpp>
#include "../I18N.hpp"
using namespace nlohmann;
namespace Slic3r
{
// Translation markers for xgettext extraction.
// These strings are configured in printers/*.json (tool_head_display_names)
// and passed to _L() at runtime. xgettext cannot scan dynamic strings,
// so we mark them here with L() for extraction into .pot file.
// This block is never executed at runtime.
static void _toolhead_translation_markers()
{
// Dynamic toolhead display names from JSON config — xgettext cannot scan these
L("Main Extruder"); L("Main extruder"); L("main extruder");
L("Auxiliary Extruder"); L("Auxiliary extruder"); L("auxiliary extruder");
L("Left Extruder"); L("Left extruder"); L("left extruder");
L("Right Extruder"); L("Right extruder"); L("right extruder");
L("Main Nozzle"); L("Main nozzle"); L("main nozzle");
L("Auxiliary Nozzle"); L("Auxiliary nozzle"); L("auxiliary nozzle");
L("Left Nozzle"); L("Left nozzle"); L("left nozzle");
L("Right Nozzle"); L("Right nozzle"); L("right nozzle");
L("Main Hotend"); L("Main hotend"); L("main hotend");
L("Auxiliary Hotend"); L("Auxiliary hotend"); L("auxiliary hotend");
L("Left Hotend"); L("Left hotend"); L("left hotend");
L("Right Hotend"); L("Right hotend"); L("right hotend");
// standalone position words (short_name=true runtime results)
L("main"); L("auxiliary");
L("Main"); L("Auxiliary");
L("left"); L("right");
L("Left"); L("Right");
}
std::string DevPrinterConfigUtil::m_resource_file_path = "";
@@ -91,7 +116,7 @@ std::string DevPrinterConfigUtil::get_filament_load_img(const std::string &type_
{
if (has_nozzle_rack)
{
const auto &rack_vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image_nozzle_rack");
const auto &rack_vec = get_value_from_config<std::vector<std::string>>(type_str, "filament_load_image_nozzle_rack") ;
if (!rack_vec.empty())
{
if (ext_id >= 0 && static_cast<size_t>(ext_id) < rack_vec.size())
@@ -111,7 +136,6 @@ std::string DevPrinterConfigUtil::get_filament_load_img(const std::string &type_
std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, const std::string& key)
{
std::vector<std::string> filaments;
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try
@@ -134,6 +158,25 @@ std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, cons
return std::string();
}
std::vector<std::string> DevPrinterConfigUtil::get_fan_text_params(const std::string& type_str, const std::string& key)
{
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try {
json jj;
if (json_file.is_open()) {
json_file >> jj;
if (jj.contains("00.00.00.00")) {
json const& printer = jj["00.00.00.00"];
if (printer.contains("fan") && printer["fan"].contains(key)) {
return printer["fan"][key].get<std::vector<std::string>>();
}
}
}
} catch (...) {}
return std::vector<std::string>();
}
std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, int airduct_mode, int airduct_func, int submode)
{
std::vector<std::string> filaments;
@@ -181,6 +224,36 @@ std::string DevPrinterConfigUtil::get_fan_text(const std::string& type_str, int
return std::string();
}
std::string DevPrinterConfigUtil::get_fan_mode_text(const std::string& type_str, int airduct_mode, const std::string& key)
{
std::vector<std::string> filaments;
std::string config_file = m_resource_file_path + "/printers/" + type_str + ".json";
boost::nowide::ifstream json_file(config_file.c_str());
try {
json jj;
if (json_file.is_open()) {
json_file >> jj;
if (jj.contains("00.00.00.00")) {
json const& printer = jj["00.00.00.00"];
if (!printer.contains("fan")) {
return std::string();
}
json const& fan_item = printer["fan"];
const auto& airduct_mode_str = std::to_string(airduct_mode);
if (!fan_item.contains(airduct_mode_str)) {
return std::string();
}
if (fan_item[airduct_mode_str].contains(key)) {
return fan_item[airduct_mode_str][key].get<std::string>();
}
}
}
} catch (...) {}
return std::string();
}
std::map<std::string, std::vector<std::string>> DevPrinterConfigUtil::get_all_subseries(std::string type_str)
{
std::map<std::string, std::vector<std::string>> subseries;
@@ -284,18 +357,29 @@ std::string DevPrinterConfigUtil::get_toolhead_display_name(
{ ToolHeadComponent::Hotend, "hotend" }
};
const int case_index = static_cast<int>(name_case);
const std::string role_key = std::to_string(ext_id);
int case_index = static_cast<int>(name_case); // 0, 1, 2
// Try to read from printer config json
auto names_json = get_value_from_config<json>(type_str, "tool_head_display_names");
std::string role_key = std::to_string(ext_id); // "0" or "1"
const std::string& comp_key = comp_keys.at(component);
std::string result;
auto names_json = get_value_from_config<json>(type_str, "tool_head_display_names");
if (!names_json.is_null() && names_json.contains(role_key) && names_json[role_key].contains(comp_key)) {
if (!names_json.is_null()
&& names_json.contains(role_key)
&& names_json[role_key].contains(comp_key))
{
auto& arr = names_json[role_key][comp_key];
if (arr.is_array() && case_index < static_cast<int>(arr.size()))
if (arr.is_array() && case_index < static_cast<int>(arr.size())) {
result = arr[case_index].get<std::string>();
}
}
// Orca: models that ship no tool_head_display_names (e.g. H2D/H2S) must still get distinct
// per-extruder labels, so fall back to the previous programmatic Left/Right construction.
// Engages only when the config lookup above yields nothing, so models that ship the key
// behave exactly like the reference.
if (result.empty()) {
const std::string side = ext_id == DEPUTY_EXTRUDER_ID ? "Left" : "Right";
const std::string component_name = component == ToolHeadComponent::Extruder ? "Extruder" :
@@ -307,13 +391,15 @@ std::string DevPrinterConfigUtil::get_toolhead_display_name(
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
}
// short_name: return only the role prefix (e.g. "Main" from "Main Nozzle")
if (short_name) {
auto sp = result.find(' ');
if (sp != std::string::npos)
if (sp != std::string::npos) {
result = result.substr(0, sp);
}
}
return result;
}
};
};
+49 -40
View File
@@ -11,6 +11,8 @@
#include <string>
#include <map>
#include <vector>
#include <algorithm>
#include <cctype>
#include <boost/log/trivial.hpp>
#include <boost/nowide/fstream.hpp>
@@ -23,6 +25,20 @@
namespace Slic3r
{
/// Toolhead component type (extruder / nozzle / hotend)
enum class ToolHeadComponent {
Extruder,
Nozzle,
Hotend
};
/// Name case style for toolhead display names
enum class ToolHeadNameCase {
TitleCase = 0, // [0] "Main Extruder" — panel titles, section headers
SentenceCase = 1, // [1] "Main extruder" — static box labels
LowerCase = 2 // [2] "main extruder" — inline text, sentence concatenation
};
class dePrinterConfigFactory
{
public:
@@ -30,17 +46,6 @@ public:
~dePrinterConfigFactory() = default;
};
enum class ToolHeadComponent {
Extruder,
Nozzle,
Hotend
};
enum class ToolHeadNameCase {
TitleCase = 0,
SentenceCase = 1,
LowerCase = 2
};
class DevPrinterConfigUtil
{
@@ -74,14 +79,19 @@ public:
static std::string get_printer_use_ams_type(std::string type_str) { return get_value_from_config<std::string>(type_str, "use_ams_type"); }
static std::string get_printer_ams_img(const std::string& type_str) { return get_value_from_config<std::string>(type_str, "printer_use_ams_image"); }
static std::string get_printer_ext_img(const std::string& type_str, int pos);//printer_ext_image
static bool support_ams_fila_change_abort(std::string type_str) { return get_value_from_config<bool>(type_str, "print", "support_ams_filament_change_abort"); }
static std::string get_filament_load_img(const std::string &type_str, int ext_id, bool has_nozzle_rack = false);
/*fan*/
static std::string get_fan_text(const std::string& type_str, const std::string& key);
static std::string get_fan_text(const std::string& type_str, const std::string& key);
static std::vector<std::string> get_fan_text_params(const std::string& type_str, const std::string& key);
static std::string get_fan_text(const std::string& type_str, int airduct_mode, int airduct_func, int submode);
static std::string get_fan_mode_text(const std::string& type_str, int airduct_mode, const std::string& key);
/*extruder*/
static bool get_printer_can_set_nozzle(std::string type_str) { return get_value_from_config<bool>(type_str, "enable_set_nozzle_info"); }// can set nozzle from studio
/*toolhead display names (extruder / nozzle / hotend)*/
static std::string get_toolhead_display_name(
const std::string& type_str,
int ext_id,
@@ -90,10 +100,15 @@ public:
bool short_name = false);
/*print job*/
static bool support_print_check_firmware_for_tpu_left(std::string type_str){ return get_value_from_config<bool>(type_str, "print", "support_print_check_firmware_for_tpu_left"); }
static bool support_user_first_setup_tpu_check(std::string type_str){ return get_value_from_config<bool>(type_str, "print", "support_user_first_setup_tpu_check"); }
static std::string support_user_first_setup_tpu_check_url(std::string type_str){ return get_value_from_config<std::string>(type_str, "print", "support_user_first_setup_tpu_check_url"); }
static bool support_ams_ext_mix_print(std::string type_str) { return get_value_from_config<bool>(type_str, "print", "support_ams_ext_mix_print"); }
static bool support_print_time_estimate_warning(std::string type_str) { return get_value_from_config<bool>(type_str, "print", "support_print_time_estimate_warning"); }
/*calibration*/
static std::vector<std::string> get_unsupport_auto_cali_filaments(std::string type_str) { return get_value_from_config<std::vector<std::string>>(type_str, "auto_cali_not_support_filaments"); }
static bool support_disable_cali_flow_type(std::string type_str) { return get_value_from_config<bool>(type_str,"support_disable_cali_flow_type"); }
/*detection*/
static bool support_wrapping_detection(const std::string& type_str) { return get_value_from_config<bool>(type_str, "support_wrapping_detection"); }
@@ -103,6 +118,10 @@ public:
/*print check*/
static bool support_print_check_extension_fan_f000_mounted(const std::string& type_str) { return get_value_from_config<bool>(type_str, "print", "support_print_check_extension_fan_f000_mounted"); }
static std::string air_print_detection_position(const std::string &type_str) { return get_value_from_config<std::string>(type_str, "air_print_detection_position"); }
/*bed*/
static int get_bed_temperature_limit(const std::string &type_str) { return get_value_from_config<int>(type_str, "print", "bed_temperature_limit"); }
public:
template<typename T>
@@ -126,7 +145,7 @@ public:
}
}
}
catch (...) { assert(0 && "get_value_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
catch (...) { assert(0 && "get_value_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
return T();
};
@@ -178,7 +197,7 @@ public:
}
}
}
catch (...) { assert(0 && "get_json_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
catch (...) { assert(0 && "get_json_from_config failed"); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed"; }// there are file errors
return nlohmann::json();
}
@@ -187,35 +206,25 @@ private:
};
/*special transform*/
static std::string _parse_printer_type(const std::string& type_str)
static std::string _parse_printer_type(const std::string &type_str)
{
if (type_str.compare("3DPrinter-X1") == 0)
{
return "BL-P002";
}
else if (type_str.compare("3DPrinter-X1-Carbon") == 0)
{
return "BL-P001";
}
else if (type_str.compare("BL-P001") == 0)
{
return type_str;
}
else if (type_str.compare("BL-P002") == 0)
{
return type_str;
}
else
{
std::string result = DevPrinterConfigUtil::get_printer_type(type_str);
if (!result.empty())
{
return result;
}
}
static std::unordered_map<std::string, std::string> s_printer_type_lazy_cache;
if (type_str == "3DPrinter-X1") return "BL-P002";
if (type_str == "3DPrinter-X1-Carbon") return "BL-P001";
if (type_str == "BL-P001" || type_str == "BL-P002") return type_str;
auto cache_it = s_printer_type_lazy_cache.find(type_str);
if (cache_it != s_printer_type_lazy_cache.end()) {
return cache_it->second;
}
std::string result = DevPrinterConfigUtil::get_printer_type(type_str);
if (!result.empty()) {
s_printer_type_lazy_cache[type_str] = result;
return result;
}
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Unsupported printer type: " << type_str;
return type_str;
}
};// namespace Slic3r
};// namespace Slic3r
+31 -25
View File
@@ -10,12 +10,21 @@
#pragma once
#include <string>
#include <utility>
#include <nlohmann/json.hpp>
// Reserved for future usage
#define DEV_RESERVED_FOR_FUTURE(...) /* stripped */
// Previous definitions
namespace Slic3r
{
class MachineObject;
}
enum PrinterArch
{
ARCH_CORE_XY,
ARCH_I3,
ARCH_CORE_XY,// move hotbed
ARCH_I3,//move z
};
enum PrinterSeries
@@ -36,6 +45,7 @@ enum AmsStatusMain
AMS_STATUS_MAIN_RFID_IDENTIFYING = 0x02,
AMS_STATUS_MAIN_ASSIST = 0x03,
AMS_STATUS_MAIN_CALIBRATION = 0x04,
AMS_STATUS_MAIN_COLD_PULL = 0x07,
AMS_STATUS_MAIN_SELF_CHECK = 0x10,
AMS_STATUS_MAIN_DEBUG = 0x20,
AMS_STATUS_MAIN_UNKNOWN = 0xFF,
@@ -51,10 +61,6 @@ enum DevAmsType : int
AMS_LITE_MIXED = 5, // AMS-Lite for N9
};
// Device-numbered filament-change step codes. Newer firmware reports the exact change-step
// sequence through the AMS (ams.cfs), keyed by these codes, instead of the client hardcoding
// steps per model. Distinct from the legacy GUI-side Slic3r::GUI::FilamentStep enum.
// STEP_CHECK_POSITION and STEP_CONFIRM_EXTRUDED share code 0x08 by device design.
enum DevFilamentStep
{
STEP_IDLE = 0x00,
@@ -83,37 +89,35 @@ enum DevFilamentStep
#define VIRTUAL_AMS_MAIN_ID_STR "255"
#define VIRTUAL_AMS_DEPUTY_ID_STR "254"
// Tray index offset for the AMS-Lite-mixed unit (A2L / N9). Its 4 trays occupy
// global tray indices 24..27.
#define AMS_LITE_MIXED_TRAY_INDEX_OFFSET 24
// Tray index offsets for different AMS types
#define AMS_LITE_MIXED_TRAY_INDEX_OFFSET 24 // Offset for AMS_LITE_MIXED tray index (24)
#define INVALID_AMS_TEMPERATURE std::numeric_limits<float>::min()
// (ams_id, slot_id) pair.
using DevAmsSlotId = std::pair<int, int>;
/* Extruder*/
#define MAIN_EXTRUDER_ID 0
#define DEPUTY_EXTRUDER_ID 1
#define UNIQUE_EXTRUDER_ID MAIN_EXTRUDER_ID
#define INVALID_EXTRUDER_ID -1
/* Logical extruder ids (multi-nozzle). */
// see PartPlate::get_physical_extruder_by_logical_extruder
#define LOGIC_UNIQUE_EXTRUDER_ID 0
#define LOGIC_L_EXTRUDER_ID 0
#define LOGIC_R_EXTRUDER_ID 1
// <ams_id, slot_id>
using DevAmsSlotId = std::pair<int, int>;
/* Nozzle*/
enum NozzleFlowType
enum NozzleFlowType : int
{
NONE_FLOWTYPE,
S_FLOW,
H_FLOW,
U_FLOW, // TPU 1.75 High Flow (device-reported; maps to nvtTPUHighFlow)
U_FLOW, // TPU 1.75 High Flow
E_FLOW, // E3D High Flow
};
// Discrete nozzle diameters reported by the device.
/* 0.2mm 0.4mm 0.6mm 0.8mm */
enum NozzleDiameterType : int
{
NONE_DIAMETER_TYPE,
@@ -135,7 +139,7 @@ enum DevPrintingSpeedLevel
};
/*Upgrade*/
enum class DevFirmwareUpgradingState : int
enum class DevFirmwareUpgradeState : int
{
DC = -1,
UpgradingUnavaliable = 0,
@@ -144,6 +148,10 @@ enum class DevFirmwareUpgradingState : int
UpgradingFinished = 3
};
// Orca: DeviceManager/UpgradePanel keep the Orca spelling of this enum; the alias to the
// reference name is a permanent compatibility surface.
using DevFirmwareUpgradingState = DevFirmwareUpgradeState;
class devPrinterUtil
{
public:
@@ -157,9 +165,6 @@ public:
namespace GUI
{
// Print source. Defined here (rather than in SelectMachine.hpp) so the shared device-mapping
// GUI headers — AmsMappingPopup, wgtDeviceNozzleSelect — can name it without pulling in
// SelectMachine.hpp, which would create an include cycle.
enum PrintFromType
{
FROM_NORMAL,
@@ -169,8 +174,6 @@ enum PrintFromType
};// namespace Slic3r
// A nozzle requirement of the sliced plate (or an installed nozzle's identity), compared in the
// pre-print slicing-vs-installed nozzle checks.
struct NozzleDef
{
float nozzle_diameter;
@@ -190,4 +193,7 @@ template<> struct std::hash<NozzleDef>
size_t h2 = std::hash<int>{}(v.nozzle_flow_type);
return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2));
};
};
};
// key(extruder_id) -> { key1(nozzle type info), val1( number of the nozzle type)}
using ExtruderNozzleInfos = std::unordered_map<int, std::unordered_map<NozzleDef, int>>;
@@ -271,7 +271,7 @@ namespace Slic3r
system->m_current_loading_extder_id = INVALID_EXTRUDER_ID;
if (!system->m_extders[MAIN_EXTRUDER_ID].m_star.ams_id.empty())
{
system->m_current_busy_for_loading = (system->m_extders[MAIN_EXTRUDER_ID].m_snow == system->m_extders[MAIN_EXTRUDER_ID].m_star);
system->m_current_busy_for_loading = (system->m_extders[MAIN_EXTRUDER_ID].m_snow != system->m_extders[MAIN_EXTRUDER_ID].m_star);
if (system->m_current_busy_for_loading)
{
system->m_current_loading_extder_id = MAIN_EXTRUDER_ID;
@@ -22,6 +22,7 @@ struct DevAmsSlotInfo
std::string slot_id;
public:
bool operator!=(const DevAmsSlotInfo& other) const { return !(*this == other); }
bool operator==(const DevAmsSlotInfo& other) const { return ams_id == other.ams_id && slot_id == other.slot_id;}
};
+2 -14
View File
@@ -46,20 +46,10 @@ void Slic3r::DevFan::converse_to_duct(bool is_suppt_part_fun, bool is_suppt_aux_
}
}
static std::string _get_string_from_fantype(int type)
{
switch (type) {
case 1: return "cooling_fan";
case 2: return "big_cooling_fan";
case 3: return "chamber_fan";
default: return "";
}
return "";
}
// Old protocol
int Slic3r::DevFan::command_control_fan(int fan_type, int val)
{
// Orca: strip analytics telemetry
std::string gcode = (boost::format("M106 P%1% S%2% \n") % (int) fan_type % (val)).str();
return m_owner->publish_gcode(gcode);
}
@@ -166,8 +156,6 @@ void Slic3r::DevFan::ParseV3_0(const json &device)
m_air_duct_data.modes.clear();
m_air_duct_data.parts.clear();
m_air_duct_data.curren_mode = device["airduct"]["modeCur"].get<int>();
const json &airduct = device["airduct"];
if (airduct.contains("modeCur")) { m_air_duct_data.curren_mode = airduct["modeCur"].get<int>(); }
if (airduct.contains("subMode")) { m_air_duct_data.m_sub_mode = airduct["subMode"].get<int>(); }
@@ -193,7 +181,7 @@ void Slic3r::DevFan::ParseV3_0(const json &device)
}
}
if (AIR_DUCT(mode.id) == AIR_DUCT::AIR_DUCT_EXHAUST) { continue; } /*STUDIO-12796*/
if (AIR_DUCT(mode.id) == AIR_DUCT::AIR_DUCT_EXHAUST) { continue; }
m_air_duct_data.modes[mode.id] = mode;
}
}
+7 -2
View File
@@ -11,8 +11,6 @@ class MachineObject;
enum AirDuctType { AIR_FAN_TYPE, AIR_DOOR_TYPE };
typedef std::function<void(const json &)> CommandCallBack;
enum AIR_FUN : int {
FAN_HEAT_BREAK_0_IDX = 0,
FAN_COOLING_0_AIRDOOR = 1,
@@ -91,6 +89,13 @@ public:
bool IsSupportCoolingFilter() const { return m_support_cooling_filter; }
bool IsCoolingFilerOn() const { return m_sub_mode == 1; }
bool IsExaustFanExit() const
{
for (auto &p : parts) {
if (p.id == int(AIR_FUN::FAN_CHAMBER_0_IDX)) return true;
}
return false;
}
};
class DevFan
@@ -1,5 +1,6 @@
#pragma once
#include <optional>
#include <unordered_map> // Orca: for m_firmwares / GetSuppotedFirmwares() — the unordered_map AMSSetting.cpp mirrors and iterates
#include <nlohmann/json.hpp>
#include "DevCtrl.h"
@@ -75,7 +76,7 @@ public:
DevAmsSystemIdx GetCurrentFirmwareIdxSel() const { return m_current_firmware_sel.m_firmare_idx; };
DevAmsSystemIdx GetCurrentFirmwareIdxRun() const { return m_current_firmware_run.m_firmare_idx; };
std::unordered_map<int, DevAmsSystemFirmware> GetSuppotedFirmwares() const { return m_firmwares;};
std::unordered_map<int, DevAmsSystemFirmware> GetSuppotedFirmwares() const { return m_firmwares;}; // Orca: unordered (mirrored by AMSSetting.cpp)
bool IsSwitching() const { return m_status == "SWITCHING";};
bool IsIdle() const { return m_status == "IDLE";};
@@ -94,7 +95,7 @@ private:
DevAmsSystemFirmware m_current_firmware_run;
DevAmsSystemFirmware m_current_firmware_sel;
std::unordered_map<int, DevAmsSystemFirmware> m_firmwares;
std::unordered_map<int, DevAmsSystemFirmware> m_firmwares; // Orca: unordered (mirrored by AMSSetting.cpp)
DevCtrlInfo m_ctrl_switching;
};
+19 -14
View File
@@ -9,19 +9,6 @@ namespace Slic3r {
DevFilaSwitch::DevFilaSwitch(MachineObject *owner) { m_owner = owner; }
bool DevFilaSwitch::IsReady() const
{
if (!m_is_installed) { return false; }
const auto& ams_list = m_owner->GetFilaSystem()->GetAmsList();
for (const auto& ams_item : ams_list) {
if (ams_item.second->GetBindedExtruderSet().empty()) { return false; }
if (!ams_item.second->GetSwitcherPos().has_value()) { return false; }
}
return true;
}
void DevFilaSwitch::Reset()
{
m_is_installed = false;
@@ -34,6 +21,24 @@ void DevFilaSwitch::Reset()
m_cali_status = CaliStatus::CALI_IDLE;
}
bool DevFilaSwitch::IsReady() const
{
if (!m_is_installed) {
BOOST_LOG_TRIVIAL(debug) << "[FilaSwitch] IsReady: not installed";
return false;
}
const auto& ams_list = m_owner->GetFilaSystem()->GetAmsList();
for (const auto& ams_item : ams_list) {
if (ams_item.second->GetBindedExtruderSet().size() == 0) { return false; }
if (!ams_item.second->GetSwitcherPos().has_value()) {
return false;
}
}
return true;
}
std::optional<DevAmsTray> DevFilaSwitch::GetInA_Slot() const
{
auto ams_id_opt = GetInA_SlotId();
@@ -128,4 +133,4 @@ void DevFilaSwitch::ParseFilaSwitchInfo(const nlohmann::json &print_jj)
}
}
}; // namespace Slic3r
}; // namespace Slic3r
+2 -12
View File
@@ -1,7 +1,6 @@
#pragma once
#include "DevDefs.h"
#include <optional>
#include <nlohmann/json.hpp>
namespace Slic3r
{
@@ -10,16 +9,11 @@ class MachineObject;
class DevExtder;
class DevAmsTray;
// Filament Track Switch (H2-family hardware).
// The filament blacklist consumes IsInstalled() for the `has_filament_switch`
// rule (e.g. PLA Silk). m_is_installed defaults to false and is only raised when
// the device reports the switch over MQTT (aux bit 29), so the rule stays inert on
// every printer that does not report an installed switch.
class DevFilaSwitch
{
public:
enum class CaliStatus : int
{
{
CALI_IDLE = 0,
CALI_STEPING = 1,
};
@@ -48,10 +42,6 @@ public:
public:
bool IsInstalled() const { return m_is_installed; };
// Ready once installed and every AMS has resolved both its extruder binding and its
// switcher track position from the device. The send dialog and sidebar sync UX only
// treat the switch as usable when this is true.
bool IsReady() const;
std::optional<bool> IsInA_HasFilament() const { return m_in_a_has_filament; };
@@ -88,4 +78,4 @@ private:
CaliStatus m_cali_status = CaliStatus::CALI_IDLE;
};
};
};
+150
View File
@@ -839,4 +839,154 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
}
}
static DevAms::AmsType ams_type_from_string(const std::string& s)
{
if (s == "ams_lite" || s == "ams-lite") return DevAms::AMS_LITE;
if (s == "n3f") return DevAms::N3F;
if (s == "n3s") return DevAms::N3S;
return DevAms::AMS; // default
}
void DevFilaSystemParser::ParseAgentFilament(const json& data, MachineObject* obj, DevFilaSystem* system)
{
if (!system || !data.is_object())
return;
// --- AMS units ---
if (data.contains("units") && data["units"].is_array())
{
std::set<std::string> seen_units;
for (const auto& u : data["units"])
{
if (!u.is_object() || !u.contains("id"))
continue;
const std::string ams_id = u.value("id", std::string());
if (ams_id.empty())
continue;
seen_units.insert(ams_id);
const int ext_id = u.value("extruder", MAIN_EXTRUDER_ID);
const DevAms::AmsType type = ams_type_from_string(u.value("type", std::string("ams")));
DevAms* ams = nullptr;
auto it = system->amsList.find(ams_id);
if (it == system->amsList.end())
{
ams = new DevAms(ams_id, ext_id, type);
system->amsList.insert(std::make_pair(ams_id, ams));
}
else
{
ams = it->second;
ams->m_ext_id = ext_id;
ams->SetAmsType(type);
}
ams->m_exist = true;
ams->m_current_temperature = u.value("temperature", (float) INVALID_AMS_TEMPERATURE);
ams->m_humidity_percent = u.value("humidity_percent", -1);
ams->m_left_dry_time = u.value("dry_time_min", 0);
// --- slots / trays ---
std::set<std::string> seen_slots;
if (u.contains("slots") && u["slots"].is_array())
{
for (const auto& s : u["slots"])
{
if (!s.is_object())
continue;
const std::string tray_id = std::to_string(s.value("index", -1));
seen_slots.insert(tray_id);
DevAmsTray* tray = nullptr;
auto tit = ams->m_trays.find(tray_id);
if (tit == ams->m_trays.end())
{
tray = new DevAmsTray(tray_id);
ams->m_trays.insert(std::make_pair(tray_id, tray));
}
else
{
tray = tit->second;
}
tray->is_exists = s.value("loaded", false);
tray->m_fila_type = s.value("material", std::string());
tray->setting_id = s.value("preset_id", std::string());
tray->UpdateColorFromStr(s.value("color", std::string()));
tray->nozzle_temp_min = std::to_string(s.value("nozzle_temp_min", 0));
tray->nozzle_temp_max = std::to_string(s.value("nozzle_temp_max", 0));
tray->remain = s.value("remain_percent", -1);
tray->k = s.value("k", 0.0f);
if (s.contains("diameter_mm") && s["diameter_mm"].is_number())
tray->diameter = std::to_string(s["diameter_mm"].get<double>());
if (s.contains("weight_g") && s["weight_g"].is_number())
tray->weight = std::to_string(s["weight_g"].get<int>());
tray->cols.clear();
if (s.contains("colors") && s["colors"].is_array())
{
for (const auto& c : s["colors"])
if (c.is_string())
tray->cols.push_back(c.get<std::string>());
}
tray->ctype = tray->cols.size() > 1 ? 1 : 0;
}
}
// prune trays no longer reported
for (auto tit = ams->m_trays.begin(); tit != ams->m_trays.end();)
{
if (seen_slots.count(tit->first) == 0)
{
delete tit->second;
tit = ams->m_trays.erase(tit);
}
else
{
++tit;
}
}
}
// prune units no longer reported
for (auto it = system->amsList.begin(); it != system->amsList.end();)
{
if (seen_units.count(it->first) == 0)
{
delete it->second;
it = system->amsList.erase(it);
}
else
{
++it;
}
}
}
// --- external / direct spools -> obj->vt_slot ---
// extruder 0 -> main virtual slot, extruder >0 -> deputy.
if (obj && data.contains("external") && data["external"].is_array())
{
obj->vt_slot.clear();
for (const auto& e : data["external"])
{
if (!e.is_object())
continue;
const int ext = e.value("extruder", MAIN_EXTRUDER_ID);
const int vt_id = (ext == MAIN_EXTRUDER_ID) ? VIRTUAL_TRAY_MAIN_ID : VIRTUAL_TRAY_DEPUTY_ID;
DevAmsTray tray(std::to_string(vt_id));
tray.is_exists = e.value("loaded", false);
tray.m_fila_type = e.value("material", std::string());
tray.setting_id = e.value("preset_id", std::string());
tray.UpdateColorFromStr(e.value("color", std::string()));
tray.nozzle_temp_min = std::to_string(e.value("nozzle_temp_min", 0));
tray.nozzle_temp_max = std::to_string(e.value("nozzle_temp_max", 0));
tray.remain = e.value("remain_percent", -1);
obj->vt_slot.push_back(tray);
}
}
}
}
+6 -4
View File
@@ -270,10 +270,10 @@ private:
AmsType m_ams_type = AmsType::AMS;
std::string m_ams_id;
int m_ext_id;//extruder id
// Orca: keeps the legacy single m_ext_id for existing consumers and carries the
// switch-aware binding alongside it (BambuStudio stores only the set). Without a Filament
// Track Switch the set is {m_ext_id}; with one installed the device binds both extruders and
// records which input track (A/B) feeds this AMS.
// Orca: pull-mode / multi-vendor agent model — keeps the legacy single m_ext_id for existing
// consumers and carries the switch-aware binding alongside it. Without a Filament Track Switch
// the set is {m_ext_id}; with one installed the device binds both extruders and records which
// input track (A/B) feeds this AMS.
std::set<int> m_binded_extruder_set;
std::optional<DevFilaSwitch::SwitchPos> m_binded_switcher_pos;
bool m_exist = false;
@@ -415,6 +415,8 @@ class DevFilaSystemParser
{
public:
static void ParseV1_0(const json& print_json, MachineObject* obj, DevFilaSystem* system, bool key_field_only);
static void ParseAgentFilament(const json& data, MachineObject* obj, DevFilaSystem* system);
};
struct DevFilamentDryingPreset
@@ -28,8 +28,8 @@ int DevFilaSystem::CtrlAmsStartDryingHour(int ams_id,
jj_command["print"]["command"] = "ams_filament_drying";
jj_command["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
jj_command["print"]["ams_id"] = ams_id;
jj_command["print"]["mode"] = static_cast<int>(DevAms::DryCtrlMode::OnTime);
jj_command["print"]["filament"] = filament_type;
jj_command["print"]["mode"] = static_cast<int>(DevAms::DryCtrlMode::OnTime); // Orca: cast scoped enum for json
jj_command["print"]["filament"] = filament_type; // Orca: fix comma-operator typo
jj_command["print"]["temp"] = tag_temp;
jj_command["print"]["duration"] = tag_duration_hour;
jj_command["print"]["humidity"] = 0;
@@ -45,8 +45,8 @@ int DevFilaSystem::CtrlAmsStopDrying(int ams_id) const
jj_command["print"]["command"] = "ams_filament_drying";
jj_command["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
jj_command["print"]["ams_id"] = ams_id;
jj_command["print"]["mode"] = static_cast<int>(DevAms::DryCtrlMode::Off);
jj_command["print"]["filament"] = "";
jj_command["print"]["mode"] = static_cast<int>(DevAms::DryCtrlMode::Off); // Orca: cast scoped enum for json
jj_command["print"]["filament"] = ""; // Orca: fix comma-operator typo
jj_command["print"]["temp"] = 0;
jj_command["print"]["duration"] = 0;
jj_command["print"]["humidity"] = 0;
+13 -4
View File
@@ -1,4 +1,7 @@
#pragma once
#include "DevFirmware.h"
#include "slic3r/Utils/json_diff.hpp"
#include <nlohmann/json.hpp>
#include <wx/string.h>
#include "slic3r/Utils/json_diff.hpp"
@@ -8,6 +11,7 @@ namespace Slic3r {
//Previous definitions
class MachineObject;
// Orca: firmware-type enum consumed by DeviceManager/UpgradePanel; no reference-side equivalent yet
enum PrinterFirmwareType
{
FIRMWARE_TYPE_ENGINEER = 0,
@@ -15,7 +19,6 @@ enum PrinterFirmwareType
FIRMEARE_TYPE_UKNOWN,
};
class FirmwareInfo
{
public:
@@ -40,12 +43,18 @@ public:
public:
bool isValid() const { return !sn.empty(); }
/*type check*/
bool isAirPump() const { return product_name.Contains("Air Pump"); }
bool isLaszer() const { return product_name.Contains("Laser"); }
bool isCuttingModule() const { return product_name.Contains("Cutting Module"); }
bool isExtinguishSystem() const { return product_name.Contains("Extinguishing System"); }
bool isWTM() const { return name.find("wtm") != std::string::npos; } // nozzle (rack hotend) module firmware
bool isFilaTrackSwitch() const { return product_name.Contains("Filament Track"); }
bool isRotary() const { return product_name.Contains("Rotary"); }// Rotary Attachment
bool isExtinguishSystem() const { return product_name.Contains("Extinguishing System"); }// Auto Fire Extinguishing System
bool isWTM() const { return name.find("wtm") != string::npos; } // nozzle
bool isExhaustFan() const { return product_name.Contains("Exhaust Fan"); }
bool isHmshub() const { return product_name.find("Filament Buffer") != string::npos; }
bool isFilaTrackSwitch() const { return product_name.find("Filament Track") != string::npos; }
};
+1
View File
@@ -29,6 +29,7 @@ bool DevHMSItem::parse_hms_info(unsigned attr, unsigned code)
std::string DevHMSItem::get_long_error_code() const
{
char buf[64];
// Orca: HMS long-error-code format excludes the reserved byte and keys the Orca HMS wiki lookups
::sprintf(buf, "%02X%02X%02X00000%1X%04X",
this->m_module_id,
this->m_module_num,
+3
View File
@@ -8,6 +8,9 @@ namespace Slic3r {
class MachineObject;
/* some static info of machine*/ /*TODO*/
// Orca: kept as a stub — connection_type/is_lan_mode_printer/is_cloud_mode_printer live inline on
// MachineObject (DeviceManager.hpp). Adopting the reference's full DevInfo would duplicate those
// definitions and require an m_dev_info member MachineObject does not have.
class DevInfo
{
public:
+39 -20
View File
@@ -24,27 +24,37 @@ namespace Slic3r
DevPrinterConfigUtil::InitFilePath(resources_dir());
// Load saved local machines
// Load saved local machines (needs an agent; when built without one the load is
// deferred to set_agent()).
if (agent) {
AppConfig* config = GUI::wxGetApp().app_config;
const auto local_machines = config->get_local_machines();
for (auto& it : local_machines) {
const auto& m = it.second;
MachineObject* obj = new MachineObject(this, m_agent, m.dev_name, m.dev_id, m.dev_ip);
obj->printer_type = m.printer_type;
obj->dev_connection_type = "lan";
obj->bind_state = "free";
obj->bind_sec_link = "secure";
obj->m_is_online = true;
obj->last_alive = Slic3r::Utils::get_current_time_utc();
obj->set_access_code(config->get("access_code", m.dev_id), false);
obj->set_user_access_code(config->get("user_access_code", m.dev_id), false);
if (obj->has_access_right()) {
localMachineList.insert(std::make_pair(m.dev_id, obj));
} else {
config->erase_local_machine(m.dev_id);
delete obj;
}
load_local_machines_from_config();
}
}
void DeviceManager::load_local_machines_from_config()
{
AppConfig* config = GUI::wxGetApp().app_config;
if (!config)
return;
const auto local_machines = config->get_local_machines();
for (auto& it : local_machines) {
const auto& m = it.second;
if (localMachineList.count(m.dev_id))
continue;
MachineObject* obj = new MachineObject(this, m_agent, m.dev_name, m.dev_id, m.dev_ip);
obj->printer_type = m.printer_type;
obj->dev_connection_type = "lan";
obj->bind_state = "free";
obj->bind_sec_link = "secure";
obj->m_is_online = true;
obj->last_alive = Slic3r::Utils::get_current_time_utc();
obj->set_access_code(config->get("access_code", m.dev_id), false);
obj->set_user_access_code(config->get("user_access_code", m.dev_id), false);
if (obj->has_access_right()) {
localMachineList.insert(std::make_pair(m.dev_id, obj));
} else {
config->erase_local_machine(m.dev_id);
delete obj;
}
}
}
@@ -98,6 +108,11 @@ namespace Slic3r
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": updating agent for "
<< localMachineList.size() << " local and "
<< userMachineList.size() << " user machines";
// First real agent after an agent-less construction (network plugin wasn't ready at
// startup): run the persisted-LAN-printer load the constructor had to skip. See
// load_local_machines_from_config().
const bool first_real_agent = (m_agent == nullptr && agent != nullptr);
m_agent = agent;
std::lock_guard<std::mutex> lock(listMutex);
@@ -111,6 +126,10 @@ namespace Slic3r
it.second->set_agent(agent);
}
}
if (first_real_agent) {
load_local_machines_from_config();
}
}
void DeviceManager::EnableMultiMachine(bool enable)
+7
View File
@@ -96,6 +96,13 @@ public:
std::map<std::string, std::vector<std::string>> device_subseries;
private:
// Load the LAN printers persisted in AppConfig into localMachineList. Runs from the
// constructor when an agent is available and, for the case where the DeviceManager was
// first built without one (network plugin not yet installed at startup), from set_agent()
// once a real agent finally arrives - so paired printers survive a plugin install/hot
// reload without an app restart.
void load_local_machines_from_config();
void keep_alive();
void check_pushing();
+2 -2
View File
@@ -162,8 +162,8 @@ namespace Slic3r
}
//first: left,nozzle=1,map=1 second: right,nozzle=0,map=2
bool right_ams_valid = ams->second->GetExtruderId() == 0 && map_opt[MappingOption::USE_RIGHT_AMS];
bool left_ams_valid = ams->second->GetExtruderId() == 1 && map_opt[MappingOption::USE_LEFT_AMS];
bool right_ams_valid = (ams->second->GetBindedExtruderSet().count(MAIN_EXTRUDER_ID) != 0) && map_opt[MappingOption::USE_RIGHT_AMS];
bool left_ams_valid = (ams->second->GetBindedExtruderSet().count(DEPUTY_EXTRUDER_ID) != 0) && map_opt[MappingOption::USE_LEFT_AMS];
if (right_ams_valid || left_ams_valid)
{
tray_filaments.emplace(std::make_pair(tray_index, info));
+2 -30
View File
@@ -16,10 +16,6 @@
#include "slic3r/GUI/GUI_App.hpp"
#include <algorithm>
#include <unordered_set>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <nlohmann/json.hpp>
using namespace nlohmann;
@@ -33,30 +29,6 @@ void MachineObject::clear_auto_nozzle_mapping()
}
}
static std::string s_get_diameter_str(float diameter)
{
return (boost::format("%.2f") % diameter).str();
}
static std::string s_get_diameter_str(const std::string& diameter)
{
try {
float dia = boost::lexical_cast<float>(diameter);
return s_get_diameter_str(dia);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to boost::lexical_cast: " << diameter;
return diameter;
}
try {
float dia = std::stof(diameter);
return s_get_diameter_str(dia);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " std::stof: " << diameter;
return diameter;
}
}
// get auto nozzle mapping through AP
// warnings:
@@ -268,7 +240,7 @@ int DevNozzleMappingCtrl::CtrlGetAutoNozzleMappingV1(Slic3r::GUI::Plater* plater
void DevNozzleMappingCtrl::ParseAutoNozzleMapping(const json& print_jj)
{
if (print_jj.contains("command") && print_jj["command"].get<std::string>() == "get_auto_nozzle_mapping") {
if (print_jj.contains("command") && print_jj["command"].get<string>() == "get_auto_nozzle_mapping") {
if (print_jj.contains("sequence_id") && print_jj["sequence_id"] == m_sequence_id) {
Clear();
DevJsonValParser::ParseVal(print_jj, "result", m_result);
@@ -482,4 +454,4 @@ float DevNozzleMappingCtrl::GetFlushWeight(Slic3r::MachineObject* obj) const
return total_flush_volume * 1.26 * 0.001;
}
} // namespace Slic3r
} // namespace Slic3r
+1 -1
View File
@@ -18,7 +18,7 @@ namespace GUI
class Plater;
}
struct FilamentInfo; // libslic3r/ProjectTask.hpp
struct FilamentInfo; // Orca: libslic3r/ProjectTask.hpp — forward-decl for CtrlGetAutoNozzleMappingV0 signature
class MachineObject;
class DevNozzleMappingCtrl
+194 -61
View File
@@ -1,11 +1,32 @@
#include "DevPrintOptions.h"
#include "DevUtil.h"
#include <cassert>
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r
{
DevPrintOptions::DevPrintOptions(MachineObject* obj) : m_obj(obj)
{
m_detection_list = {
{PrintOptionEnum::AI_Monitoring, &m_ai_monitoring_detection},
{PrintOptionEnum::First_Layer_Detection, &m_first_layer_detection},
{PrintOptionEnum::Buildplate_Mark_Detection, &m_buildplate_mark_detection},
{PrintOptionEnum::Buildplate_Align_Detection, &m_buildplate_align_detection},
{PrintOptionEnum::Auto_Recovery_Detection, &m_auto_recovery_detection},
{PrintOptionEnum::Allow_Prompt_Sound_Detection, &m_allow_prompt_sound_detection},
{PrintOptionEnum::Filament_Tangle_Detection, &m_filament_tangle_detection},
{PrintOptionEnum::Idle_Heating_Protect_Detection, &m_idel_heating_protect_detection},
{PrintOptionEnum::Purify_Air_At_Print_End, &m_purify_air_at_print_end},
{PrintOptionEnum::Snapshot_Detection, &m_snapshot_detection},
{PrintOptionEnum::FOD_Check_Detection, &m_fod_check_detection},
{PrintOptionEnum::Displacement_Detection, &m_displacement_detection},
{PrintOptionEnum::Smart_Nozzle_Blob_Detection, &m_smart_nozzle_blob_detection},
};
}
void DevPrintOptionsParser::Parse(DevPrintOptions* opts, const nlohmann::json& print_json)
{
try
@@ -20,6 +41,31 @@ void DevPrintOptionsParser::Parse(DevPrintOptions* opts, const nlohmann::json& p
const std::string& cfg = print_json["cfg"].get<std::string>();
opts->m_speed_level = (DevPrintingSpeedLevel)DevUtil::get_flag_bits(cfg, 8, 3);
}
// Orca: xcam.cfg is an integer bitfield (distinct from the top-level cfg string) that
// carries the plate-align/FOD/displacement current values.
if (print_json.contains("xcam") && print_json["xcam"].contains("cfg")) {
int xcam_cfg = print_json["xcam"]["cfg"].get<int>();
if (time(nullptr) - opts->m_buildplate_align_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_buildplate_align_detection.current_detect_value = DevUtil::get_flag_bits(xcam_cfg, 20);
if (time(nullptr) - opts->m_fod_check_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_fod_check_detection.current_detect_value = DevUtil::get_flag_bits(xcam_cfg, 21);
if (time(nullptr) - opts->m_displacement_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_displacement_detection.current_detect_value = DevUtil::get_flag_bits(xcam_cfg, 22);
}
// Orca: fun2 support bits (distinct from the top-level cfg/fun fields). ORCA's DevUtil has no
// no-border extractor, so the MachineObject helper is used here instead.
if (print_json.contains("fun2") && print_json["fun2"].is_string()) {
std::string fun2 = print_json["fun2"].get<std::string>();
if (!fun2.empty()) {
opts->m_buildplate_align_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 2) == 1;
opts->m_purify_air_at_print_end.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 4);
opts->m_fod_check_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 13);
opts->m_displacement_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 14);
opts->m_smart_nozzle_blob_detection.is_support_detect = opts->m_obj->get_flag_bits_no_border(fun2, 15);
}
}
}
catch (const std::exception& e)
{
@@ -31,32 +77,32 @@ void DevPrintOptionsParser::ParseDetectionV1_0(DevPrintOptions *opts, MachineObj
{
try {
if (print_json.contains("xcam")) {
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_TIME_3SEC) {
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("printing_monitor")) {
// new protocol
opts->xcam_ai_monitoring = print_json["xcam"]["printing_monitor"].get<bool>();
opts->m_ai_monitoring_detection.current_detect_value = print_json["xcam"]["printing_monitor"].get<bool>();
} else {
// old version protocol
if (print_json["xcam"].contains("spaghetti_detector")) {
opts->xcam_ai_monitoring = print_json["xcam"]["spaghetti_detector"].get<bool>();
opts->m_ai_monitoring_detection.current_detect_value = print_json["xcam"]["spaghetti_detector"].get<bool>();
if (print_json["xcam"].contains("print_halt")) {
bool print_halt = print_json["xcam"]["print_halt"].get<bool>();
if (print_halt) { opts->xcam_ai_monitoring_sensitivity = "medium"; }
if (print_halt) { opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "medium"; }
}
}
}
if (print_json["xcam"].contains("halt_print_sensitivity")) {
opts->xcam_ai_monitoring_sensitivity = print_json["xcam"]["halt_print_sensitivity"].get<std::string>();
opts->m_ai_monitoring_detection.current_detect_sensitivity_value = print_json["xcam"]["halt_print_sensitivity"].get<std::string>();
}
}
if (time(nullptr) - opts->xcam_first_layer_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("first_layer_inspector")) { opts->xcam_first_layer_inspector = print_json["xcam"]["first_layer_inspector"].get<bool>(); }
if (time(nullptr) - opts->m_first_layer_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("first_layer_inspector")) { opts->m_first_layer_detection.current_detect_value = print_json["xcam"]["first_layer_inspector"].get<bool>(); }
}
if (time(nullptr) - opts->xcam_buildplate_marker_hold_start > HOLD_TIME_3SEC) {
if (time(nullptr) - opts->m_buildplate_mark_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json["xcam"].contains("buildplate_marker_detector")) {
opts->xcam_buildplate_marker_detector = print_json["xcam"]["buildplate_marker_detector"].get<bool>();
opts->m_buildplate_mark_detection.current_detect_value = print_json["xcam"]["buildplate_marker_detector"].get<bool>();
obj->is_support_build_plate_marker_detect = true;
} else {
obj->is_support_build_plate_marker_detect = false;
@@ -66,32 +112,31 @@ void DevPrintOptionsParser::ParseDetectionV1_0(DevPrintOptions *opts, MachineObj
} catch (...) {
;
}
}
void DevPrintOptionsParser::ParseDetectionV1_1(DevPrintOptions *opts, MachineObject *obj, const nlohmann::json &print_json,bool enable)
{
if (print_json["module_name"].get<std::string>() == "first_layer_inspector") {
if (time(nullptr) - opts->xcam_first_layer_hold_start > HOLD_TIME_3SEC) {
opts->xcam_first_layer_inspector = enable;
if (time(nullptr) - opts->m_first_layer_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_first_layer_detection.current_detect_value = enable;
}
} else if (print_json["module_name"].get<std::string>() == "buildplate_marker_detector") {
if (time(nullptr) - opts->xcam_buildplate_marker_hold_start > HOLD_TIME_3SEC) {
opts->xcam_buildplate_marker_detector = enable;
if (time(nullptr) - opts->m_buildplate_mark_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_buildplate_mark_detection.current_detect_value = enable;
}
} else if (print_json["module_name"].get<std::string>() == "printing_monitor") {
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_TIME_3SEC) {
opts->xcam_ai_monitoring = enable;
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_ai_monitoring_detection.current_detect_value = enable;
if (print_json.contains("halt_print_sensitivity")) {
opts->xcam_ai_monitoring_sensitivity = print_json["halt_print_sensitivity"].get<std::string>();
opts->m_ai_monitoring_detection.current_detect_sensitivity_value = print_json["halt_print_sensitivity"].get<std::string>();
}
}
} else if (print_json["module_name"].get<std::string>() == "spaghetti_detector") {
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_TIME_3SEC) {
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_TIME_3SEC) {
// old protocol
opts->xcam_ai_monitoring = enable;
opts->m_ai_monitoring_detection.current_detect_value = enable;
if (print_json.contains("print_halt")) {
if (print_json["print_halt"].get<bool>()) { opts->xcam_ai_monitoring_sensitivity = "medium"; }
if (print_json["print_halt"].get<bool>()) { opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "medium"; }
}
}
}
@@ -103,12 +148,12 @@ void DevPrintOptionsParser::ParseDetectionV1_2(DevPrintOptions *opts, MachineObj
if (print_json.contains("option")) {
if (print_json["option"].is_number()) {
int option = print_json["option"].get<int>();
if (time(nullptr) - opts->xcam_auto_recovery_hold_start > HOLD_TIME_3SEC) { opts->xcam_auto_recovery_step_loss = ((option & 0x01) != 0); }
if (time(nullptr) - opts->m_auto_recovery_detection.detect_hold_start > HOLD_TIME_3SEC) { opts->m_auto_recovery_detection.current_detect_value = ((option & 0x01) != 0); }
}
}
if (time(nullptr) - opts->xcam_auto_recovery_hold_start > HOLD_TIME_3SEC) {
if (print_json.contains("auto_recovery")) { opts->xcam_auto_recovery_step_loss = print_json["auto_recovery"].get<bool>(); }
if (time(nullptr) - opts->m_auto_recovery_detection.detect_hold_start > HOLD_TIME_3SEC) {
if (print_json.contains("auto_recovery")) { opts->m_auto_recovery_detection.current_detect_value = print_json["auto_recovery"].get<bool>(); }
}
} catch (...) {}
@@ -117,38 +162,52 @@ void DevPrintOptionsParser::ParseDetectionV1_2(DevPrintOptions *opts, MachineObj
void DevPrintOptionsParser::ParseDetectionV2_0(DevPrintOptions *opts, std::string print_json)
{
if (time(nullptr) - opts->xcam_first_layer_hold_start > HOLD_TIME_3SEC) {
opts->xcam_first_layer_inspector = DevUtil::get_flag_bits(print_json, 12);
if (time(nullptr) - opts->m_first_layer_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_first_layer_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 12);
}
if (time(nullptr) - opts->xcam_ai_monitoring_hold_start > HOLD_COUNT_MAX) {
opts->xcam_ai_monitoring = DevUtil::get_flag_bits(print_json, 15);
if (time(nullptr) - opts->m_ai_monitoring_detection.detect_hold_start > HOLD_COUNT_MAX) {
opts->m_ai_monitoring_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 15);
switch (DevUtil::get_flag_bits(print_json, 13, 2)) {
case 0: opts->xcam_ai_monitoring_sensitivity = "never_halt"; break;
case 1: opts->xcam_ai_monitoring_sensitivity = "low"; break;
case 2: opts->xcam_ai_monitoring_sensitivity = "medium"; break;
case 3: opts->xcam_ai_monitoring_sensitivity = "high"; break;
case 0: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "never_halt"; break;
case 1: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "low"; break;
case 2: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "medium"; break;
case 3: opts->m_ai_monitoring_detection.current_detect_sensitivity_value = "high"; break;
default: break;
}
}
if (time(nullptr) - opts->xcam_auto_recovery_hold_start > HOLD_COUNT_MAX){
opts->xcam_auto_recovery_step_loss =DevUtil::get_flag_bits(print_json, 16);
if (time(nullptr) - opts->m_auto_recovery_detection.detect_hold_start > HOLD_COUNT_MAX){
opts->m_auto_recovery_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 16);
}
if (time(nullptr) - opts->xcam_prompt_sound_hold_start > HOLD_TIME_3SEC) {
opts->xcam_allow_prompt_sound = DevUtil::get_flag_bits(print_json, 22);
if (time(nullptr) - opts->m_allow_prompt_sound_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_allow_prompt_sound_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 22);
}
if (time(nullptr) - opts->xcam_filament_tangle_detect_hold_start > HOLD_TIME_3SEC) {
opts->xcam_filament_tangle_detect = DevUtil::get_flag_bits(print_json, 23);
if (time(nullptr) - opts->m_filament_tangle_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_filament_tangle_detection.current_detect_value = DevUtil::get_flag_bits(print_json, 23);
}
}
void DevPrintOptionsParser::ParseDetectionV2_1(DevPrintOptions *opts, std::string cfg) {
if (time(nullptr) - opts->idel_heating_protect_hold_strat > HOLD_TIME_3SEC)
opts->idel_heating_protect_enabled = DevUtil::get_flag_bits(cfg, 32, 2);
if (time(nullptr) - opts->m_idel_heating_protect_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_idel_heating_protect_detection.current_detect_value = DevUtil::get_flag_bits(cfg, 32, 2);
// Orca: further top-level cfg-string detection bits, parsed here alongside idle-heating.
if (time(nullptr) - opts->m_purify_air_at_print_end.detect_hold_start > HOLD_TIME_3SEC)
opts->m_purify_air_at_print_end.current_detect_value = DevUtil::get_flag_bits(cfg, 36, 2);
// Smart nozzle blob detection mode: cfg bits 43-44 (2 bits), 0=off, 1=on, 2=auto
if (time(nullptr) - opts->m_smart_nozzle_blob_detection.detect_hold_start > HOLD_TIME_3SEC)
opts->m_smart_nozzle_blob_detection.current_detect_value = DevUtil::get_flag_bits(cfg, 43, 2);
if (time(nullptr) - opts->m_snapshot_detection.detect_hold_start > HOLD_TIME_3SEC) {
opts->m_snapshot_detection.current_detect_value = DevUtil::get_flag_bits(cfg, 38, 2);
opts->m_snapshot_detection.is_support_detect =
opts->m_snapshot_detection.current_detect_value != 0 && opts->m_snapshot_detection.current_detect_value != 3;
}
}
void DevPrintOptions::SetPrintingSpeedLevel(DevPrintingSpeedLevel speed_level)
@@ -163,75 +222,85 @@ void DevPrintOptions::SetPrintingSpeedLevel(DevPrintingSpeedLevel speed_level)
}
}
PrintOptionData* DevPrintOptions::GetDetectionOption(PrintOptionEnum print_option)
{
auto it = m_detection_list.find(print_option);
if (it != m_detection_list.end())
{
return it->second;
}
return nullptr;
}
int DevPrintOptions::command_xcam_control_ai_monitoring(bool on_off, std::string lvl)
{
bool print_halt = (lvl == "never_halt") ? false : true;
xcam_ai_monitoring = on_off;
xcam_ai_monitoring_hold_start = time(nullptr);
xcam_ai_monitoring_sensitivity = lvl;
m_ai_monitoring_detection.current_detect_value = on_off;
m_ai_monitoring_detection.detect_hold_start = time(nullptr);
m_ai_monitoring_detection.current_detect_sensitivity_value = lvl;
return command_xcam_control("printing_monitor", on_off, m_obj, lvl);
}
int DevPrintOptions::command_xcam_control_idelheatingprotect_detector(bool on_off)
{
idel_heating_protect_enabled = on_off;
idel_heating_protect_hold_strat = time(nullptr);
m_idel_heating_protect_detection.current_detect_value = on_off;
m_idel_heating_protect_detection.detect_hold_start = time(nullptr);
return command_set_against_continued_heating_mode(on_off);
}
int DevPrintOptions::command_xcam_control_buildplate_marker_detector(bool on_off)
{
xcam_buildplate_marker_detector = on_off;
xcam_buildplate_marker_hold_start = time(nullptr);
m_buildplate_mark_detection.current_detect_value = on_off;
m_buildplate_mark_detection.detect_hold_start = time(nullptr);
return command_xcam_control("buildplate_marker_detector", on_off ,m_obj);
}
int DevPrintOptions::command_xcam_control_first_layer_inspector(bool on_off, bool print_halt)
{
xcam_first_layer_inspector = on_off;
xcam_first_layer_hold_start = time(nullptr);
m_first_layer_detection.current_detect_value = on_off;
m_first_layer_detection.detect_hold_start = time(nullptr);
return command_xcam_control("first_layer_inspector", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_auto_recovery_step_loss(bool on_off)
{
xcam_auto_recovery_step_loss = on_off;
xcam_auto_recovery_hold_start = time(nullptr);
m_auto_recovery_detection.current_detect_value = on_off;
m_auto_recovery_detection.detect_hold_start = time(nullptr);
return command_set_printing_option(on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_allow_prompt_sound(bool on_off)
{
xcam_allow_prompt_sound = on_off;
xcam_prompt_sound_hold_start = time(nullptr);
m_allow_prompt_sound_detection.current_detect_value = on_off;
m_allow_prompt_sound_detection.detect_hold_start = time(nullptr);
return command_set_prompt_sound(on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_filament_tangle_detect(bool on_off)
{
xcam_filament_tangle_detect = on_off;
xcam_filament_tangle_detect_hold_start = time(nullptr);
m_filament_tangle_detection.current_detect_value = on_off;
m_filament_tangle_detection.detect_hold_start = time(nullptr);
return command_set_filament_tangle_detect(on_off, m_obj);
}
void DevPrintOptions::parse_auto_recovery_step_loss_status(int flag) {
if (time(nullptr) - xcam_auto_recovery_hold_start > HOLD_TIME_3SEC) {
xcam_auto_recovery_step_loss = ((flag >> 4) & 0x1) != 0;
if (time(nullptr) - m_auto_recovery_detection.detect_hold_start > HOLD_TIME_3SEC) {
m_auto_recovery_detection.current_detect_value = ((flag >> 4) & 0x1) != 0;
}
}
void DevPrintOptions::parse_allow_prompt_sound_status(int flag)
{
if (time(nullptr) - xcam_prompt_sound_hold_start > HOLD_TIME_3SEC) {
xcam_allow_prompt_sound = ((flag >> 17) & 0x1) != 0;
if (time(nullptr) - m_allow_prompt_sound_detection.detect_hold_start > HOLD_TIME_3SEC) {
m_allow_prompt_sound_detection.current_detect_value = ((flag >> 17) & 0x1) != 0;
}
}
void DevPrintOptions::parse_filament_tangle_detect_status(int flag)
{
if (time(nullptr) - xcam_filament_tangle_detect_hold_start > HOLD_TIME_3SEC) {
xcam_filament_tangle_detect = ((flag >> 20) & 0x1) != 0;
if (time(nullptr) - m_filament_tangle_detection.detect_hold_start > HOLD_TIME_3SEC) {
m_filament_tangle_detection.current_detect_value = ((flag >> 20) & 0x1) != 0;
}
}
@@ -289,11 +358,75 @@ int DevPrintOptions::command_set_filament_tangle_detect(bool filament_tangle_det
return obj->publish_json(j);
}
int DevPrintOptions::command_xcam_control_build_plate_align_detector(bool on_off)
{
m_buildplate_align_detection.current_detect_value = on_off;
m_buildplate_align_detection.detect_hold_start = time(nullptr);
return command_xcam_control("plate_offset_switch", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_fod_check(bool on_off)
{
m_fod_check_detection.current_detect_value = on_off;
m_fod_check_detection.detect_hold_start = time(nullptr);
return command_xcam_control("fod_check", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_displacement_detection(bool on_off)
{
m_displacement_detection.current_detect_value = on_off;
m_displacement_detection.detect_hold_start = time(nullptr);
return command_xcam_control("model_movement_check", on_off, m_obj);
}
int DevPrintOptions::command_xcam_control_purify_air_at_print_end(int on_off)
{
m_purify_air_at_print_end.current_detect_value = on_off;
m_purify_air_at_print_end.detect_hold_start = time(nullptr);
return command_set_purify_air_at_print_end((PurifyAirAtPrintEndState) on_off, m_obj);
}
int DevPrintOptions::command_set_purify_air_at_print_end(PurifyAirAtPrintEndState state, MachineObject *obj)
{
json j;
j["print"]["command"] = "print_option";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
switch (state)
{
case PurifyAirAtPrintEndState::PurifyAirDisable: j["print"]["air_purification"] = 0; break;
case PurifyAirAtPrintEndState::PurifyAirByInside: j["print"]["air_purification"] = 1; break;
case PurifyAirAtPrintEndState::PurifyAirByOutside: j["print"]["air_purification"] = 2; break;
default: assert(0);
}
return obj->publish_json(j);
}
int DevPrintOptions::command_snapshot_control(int on_off)
{
m_snapshot_detection.current_detect_value = on_off;
m_snapshot_detection.detect_hold_start = time(nullptr);
return command_set_snapshot_control(on_off, m_obj);
}
int DevPrintOptions::command_set_snapshot_control(int on_off, MachineObject *obj)
{
json j;
j["camera"]["command"] = "ipcam_cap_pic_set";
j["camera"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["camera"]["control"] = on_off ? "enable" : "disable";
return obj->publish_json(j);
}
int DevPrintOptions::command_smart_nozzle_blob_detect_mode(int mode)
{
json j;
j["print"]["command"] = "print_option";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["print"]["nozzle_blob_detect_v2"] = mode; // int: 0=off, 1=on, 2=auto
m_smart_nozzle_blob_detection.current_detect_value = mode;
m_smart_nozzle_blob_detection.detect_hold_start = time(nullptr);
return m_obj->publish_json(j);
}
}
// namespace Slic3r
// namespace Slic3r
+72 -26
View File
@@ -1,4 +1,5 @@
#pragma once
#include <map>
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include <wx/string.h>
@@ -9,17 +10,53 @@ namespace Slic3r {
class MachineObject;
// Identifies a print-detection option tracked in DevPrintOptions::m_detection_list.
enum class PrintOptionEnum
{
AI_Monitoring,
First_Layer_Detection,
Buildplate_Mark_Detection,
Buildplate_Align_Detection,
Auto_Recovery_Detection,
Allow_Prompt_Sound_Detection,
Filament_Tangle_Detection,
Idle_Heating_Protect_Detection,
Purify_Air_At_Print_End,
Snapshot_Detection,
FOD_Check_Detection,
Displacement_Detection,
Smart_Nozzle_Blob_Detection,
};
// State of a single print-detection option.
struct PrintOptionData
{
bool is_support_detect{false}; // some detections have no supporting field
int current_detect_value{-1}; // -1 until parsed; otherwise the reported value
std::string current_detect_sensitivity_value;
time_t detect_hold_start{0}; // suppresses parsed overwrites for a short hold
};
class DevPrintOptions
{
friend class DevPrintOptionsParser;
public:
DevPrintOptions(MachineObject* obj): m_obj(obj) {}
DevPrintOptions(MachineObject* obj);
// Tri-state for the purify-air-at-print-end firmware option (matches the MQTT air_purification value).
enum class PurifyAirAtPrintEndState : int {
PurifyAirDisable = 0,
PurifyAirByInside = 1,
PurifyAirByOutside = 2,
};
public:
void SetPrintingSpeedLevel(DevPrintingSpeedLevel speed_level);
DevPrintingSpeedLevel GetPrintingSpeedLevel() const { return m_speed_level;}
// Returns the holder for a tracked detection option, or nullptr if not tracked.
PrintOptionData* GetDetectionOption(PrintOptionEnum print_option);
// detect options
int command_xcam_control_ai_monitoring(bool on_off, std::string lvl);
int command_xcam_control_first_layer_inspector(bool on_off, bool print_halt);
@@ -29,6 +66,13 @@ public:
int command_xcam_control_filament_tangle_detect(bool on_off);
int command_xcam_control_idelheatingprotect_detector(bool on_off);
// Firmware print-option toggles (each gated on a fun2 capability bit).
int command_xcam_control_build_plate_align_detector(bool on_off);
int command_xcam_control_fod_check(bool on_off);
int command_xcam_control_displacement_detection(bool on_off);
int command_xcam_control_purify_air_at_print_end(int on_off);
int command_smart_nozzle_blob_detect_mode(int mode); // 0=off, 1=on, 2=auto
int command_snapshot_control(int on_off);
int command_xcam_control(std::string module_name, bool on_off, MachineObject *obj ,std::string lvl = "");
// set print option
@@ -39,42 +83,44 @@ public:
int command_set_filament_tangle_detect(bool fliament_tangle_detect, MachineObject *obj);
int command_set_against_continued_heating_mode(bool on_off);
int command_set_purify_air_at_print_end(PurifyAirAtPrintEndState state, MachineObject *obj);
int command_set_snapshot_control(int on_off, MachineObject *obj);
void parse_auto_recovery_step_loss_status(int flag);
void parse_allow_prompt_sound_status(int flag);
void parse_filament_tangle_detect_status(int flag);
bool GetAiMonitoring() const { return xcam_ai_monitoring; }
bool GetFirstLayerInspector() const{ return xcam_first_layer_inspector; }
bool GetBuildplateMarkerDetector() const { return xcam_buildplate_marker_detector; }
bool GetAutoRecoveryStepLoss() const { return xcam_auto_recovery_step_loss; }
bool GetAllowPromptSound() const { return xcam_allow_prompt_sound; }
bool GetFilamentTangleDetect() const { return xcam_filament_tangle_detect; }
int GetIdelHeatingProtectEenabled() const { return idel_heating_protect_enabled; }
string GetAiMonitoringSensitivity() const { return xcam_ai_monitoring_sensitivity; }
// Thin wrappers preserved for existing callers; read from the detection-option map.
bool GetAiMonitoring() const { return m_ai_monitoring_detection.current_detect_value == 1; }
bool GetFirstLayerInspector() const{ return m_first_layer_detection.current_detect_value == 1; }
bool GetBuildplateMarkerDetector() const { return m_buildplate_mark_detection.current_detect_value == 1; }
bool GetAutoRecoveryStepLoss() const { return m_auto_recovery_detection.current_detect_value == 1; }
bool GetAllowPromptSound() const { return m_allow_prompt_sound_detection.current_detect_value == 1; }
bool GetFilamentTangleDetect() const { return m_filament_tangle_detection.current_detect_value == 1; }
int GetIdelHeatingProtectEenabled() const { return m_idel_heating_protect_detection.current_detect_value; }
std::string GetAiMonitoringSensitivity() const { return m_ai_monitoring_detection.current_detect_sensitivity_value; }
private:
// print option
DevPrintingSpeedLevel m_speed_level = SPEED_LEVEL_INVALID;
// detect options
bool xcam_ai_monitoring{false};
// detection options
PrintOptionData m_ai_monitoring_detection;
PrintOptionData m_first_layer_detection;
PrintOptionData m_buildplate_mark_detection;
PrintOptionData m_buildplate_align_detection;
PrintOptionData m_auto_recovery_detection;
PrintOptionData m_allow_prompt_sound_detection;
PrintOptionData m_filament_tangle_detection;
PrintOptionData m_idel_heating_protect_detection;
PrintOptionData m_purify_air_at_print_end;
PrintOptionData m_snapshot_detection;
PrintOptionData m_fod_check_detection;
PrintOptionData m_displacement_detection;
PrintOptionData m_smart_nozzle_blob_detection;
std::string xcam_ai_monitoring_sensitivity;
bool xcam_buildplate_marker_detector{false};
bool xcam_first_layer_inspector{false};
bool xcam_auto_recovery_step_loss{false};
bool xcam_allow_prompt_sound{false};
bool xcam_filament_tangle_detect{false};
int idel_heating_protect_enabled = -1;
time_t xcam_ai_monitoring_hold_start = 0;
time_t xcam_buildplate_marker_hold_start = 0;
time_t xcam_first_layer_hold_start = 0;
time_t xcam_auto_recovery_hold_start = 0;
time_t xcam_prompt_sound_hold_start = 0;
time_t xcam_filament_tangle_detect_hold_start = 0;
time_t idel_heating_protect_hold_strat = 0;
std::map<PrintOptionEnum, PrintOptionData*> m_detection_list;
MachineObject* m_obj;/*owner*/
};
@@ -93,4 +139,4 @@ public:
static void ParseDetectionV2_1(DevPrintOptions *opts, std::string cfg);
};
} // namespace Slic3r
} // namespace Slic3r
+33
View File
@@ -0,0 +1,33 @@
#include "DevStatus.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
void DevStatus::ParseStatus(const nlohmann::json& print_jj)
{
try {
if (print_jj.contains("job")) {
const nlohmann::json& job_jj = print_jj.at("job");
if (job_jj.contains("job_state")) {
auto new_state = (DevJobState)job_jj.at("job_state").get<int>();
if (m_job_state != new_state) {
m_job_state = new_state;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": job_state-> " << (int)new_state;
}
if (m_job_state == DevJobState::JobStateFinishing) {
m_job_state = DevJobState::JobStateFinish;
}
}
}
} catch (const std::exception& e) {
#if BBL_RELEASE_TO_PUBLIC
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get exception";
#else
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": get exception=" << e.what();
#endif
}
}
} // namespace Slic3r
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <optional>
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include "DevDefs.h"
namespace Slic3r {
enum DevJobState
{
JobStateIdle = 0,
JobStateSlicing = 1,
JobStatePrepare = 2,
JobStateStarting = 3,
JobStateRunning = 4,
JobStatePause = 5,
JobStatePausing = 6,
JobStateResuming = 7,
JobStateFinish = 8,
JobStateFailed = 9,
JobStateFinishing = 10,
JobStateStoppping = 11,
};
class MachineObject;
class DevStatus
{
public:
DevStatus(MachineObject* owner) : m_owner(owner) {};
public:
std::optional<DevJobState> GetJobState() const { return m_job_state; }
// Parse
void ParseStatus(const nlohmann::json& print_jj);
private:
MachineObject *m_owner = nullptr;
std::optional<DevJobState> m_job_state; // could be nullopt for some old firmware
};
} // namespace Slic3r
+34 -8
View File
@@ -19,16 +19,42 @@ DevStorage::SdcardState Slic3r::DevStorage::set_sdcard_state(int state)
{
if (system)
{
if (print_json.contains("sdcard")) {
if (print_json["sdcard"].get<bool>())
system->m_sdcard_state = DevStorage::SdcardState::HAS_SDCARD_NORMAL;
else
try {
if (print_json.contains("sdcard")) {
if (print_json["sdcard"].get<bool>())
system->m_sdcard_state = DevStorage::SdcardState::HAS_SDCARD_NORMAL;
else
system->m_sdcard_state = DevStorage::SdcardState::NO_SDCARD;
} else {
system->m_sdcard_state = DevStorage::SdcardState::NO_SDCARD;
} else {
system->m_sdcard_state = DevStorage::SdcardState::NO_SDCARD;
}
}
}
// parse timelapse storage space from cam push data
if (print_json.contains("device") && print_json["device"].is_object()) {
const auto& device_json = print_json["device"];
if (device_json.contains("cam") && device_json["cam"].is_object()) {
const auto& cam_data = device_json["cam"];
if (cam_data.contains("tl_internal_free_kb"))
system->tl_internal_free_kb = cam_data["tl_internal_free_kb"].get<int>();
if (cam_data.contains("tl_internal_total_kb"))
system->tl_internal_total_kb = cam_data["tl_internal_total_kb"].get<int>();
if (cam_data.contains("tl_external_free_kb"))
system->tl_external_free_kb = cam_data["tl_external_free_kb"].get<int>();
if (cam_data.contains("tl_external_total_kb"))
system->tl_external_total_kb = cam_data["tl_external_total_kb"].get<int>();
}
}
} catch (...) {}
}
}
bool DevStorage::is_timelapse_storage_low(const std::string& storage) const
{
const int THRESHOLD_KB = 20480; // Orca: 20MB (20480 KB; corrected mislabeled comment)
if (storage == "internal")
return tl_internal_free_kb >= 0 && tl_internal_free_kb < THRESHOLD_KB;
else
return tl_external_free_kb >= 0 && tl_external_free_kb < THRESHOLD_KB;
}
} // namespace Slic3r
+11 -1
View File
@@ -26,11 +26,21 @@ public:
SdcardState get_sdcard_state() const { return m_sdcard_state; };
SdcardState set_sdcard_state(int state);
static void ParseV1_0(const json &print_json, DevStorage *system);
static void ParseV1_0(const json &print_json, DevStorage *system);
bool is_timelapse_storage_low(const std::string& storage) const;
private:
MachineObject *m_owner;
SdcardState m_sdcard_state { NO_SDCARD };
// timelapse storage space info (from device push cam data)
int tl_internal_free_kb{-1};
int tl_internal_total_kb{-1};
int tl_external_free_kb{-1};
int tl_external_total_kb{-1};
};
} // namespace Slic3r
+130
View File
@@ -0,0 +1,130 @@
#include "DevUpgrade.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
namespace Slic3r {
int DevUpgrade::GetUpgradeProgressInt() const
{
try {
return std::stoi(m_upgrade_progress);
} catch (const std::exception &e) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": " << e.what();
}
return 0;
}
void DevUpgrade::ParseUpgrade_V1_0(const json &print_jj)
{
if (print_jj.contains("upgrade_state")) {
const auto &upgrade_jj = print_jj["upgrade_state"];
// status and steps
DevJsonValParser::ParseVal(upgrade_jj, "status", m_upgrade_status);
DevJsonValParser::ParseVal(upgrade_jj, "progress", m_upgrade_progress);
DevJsonValParser::ParseVal(upgrade_jj, "message", m_upgrade_message);
DevJsonValParser::ParseVal(upgrade_jj, "module", m_upgrade_module);
DevJsonValParser::ParseVal(upgrade_jj, "err_code", m_upgrade_err_code);
// version requested
DevJsonValParser::ParseVal(upgrade_jj, "new_version_state", m_upgrade_new_version_state);
DevJsonValParser::ParseVal(upgrade_jj, "consistency_request", m_upgrade_consistency_request);
DevJsonValParser::ParseVal(upgrade_jj, "force_upgrade", m_upgrade_force_upgrade);
// version info
DevJsonValParser::ParseVal(upgrade_jj, "ota_new_version_number", m_ota_new_version_number);
// version list
if (upgrade_jj.contains("new_ver_list")) {
m_new_ver_list.clear();
for (auto ver_item = upgrade_jj["new_ver_list"].begin(); ver_item != upgrade_jj["new_ver_list"].end(); ver_item++) {
DevFirmwareVersionInfo ver_info;
DevJsonValParser::ParseVal(*ver_item, "name", ver_info.name);
DevJsonValParser::ParseVal(*ver_item, "cur_ver", ver_info.sw_ver);
DevJsonValParser::ParseVal(*ver_item, "new_ver", ver_info.sw_new_ver);
m_new_ver_list[ver_info.name] = ver_info;
if (ver_info.name == "ota" && m_ota_new_version_number.empty()) { m_ota_new_version_number = ver_info.sw_new_ver; }
}
} else {
m_new_ver_list.clear();
}
// try to parse display state
ParseUpgradeDisplayState(upgrade_jj);
}
if (print_jj.contains("cfg")) {
const std::string &cfg = print_jj["cfg"].get<std::string>();
if (!cfg.empty()) { m_upgrade_force_upgrade = DevUtil::get_flag_bits(cfg, 2); }
}
}
void DevUpgrade::ParseUpgradeDisplayState(const json &upgrade_state_jj)
{
if (upgrade_state_jj.contains("dis_state")) {
if ((int) m_upgrade_display_state != upgrade_state_jj["dis_state"].get<int>()) {
DevJsonValParser::ParseVal(upgrade_state_jj, "dis_state", m_upgrade_display_state);
// update the version after upgrade finished
if (m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingFinished) {
Slic3r::GUI::wxGetApp().CallAfter([this] {
m_owner->command_get_version();
});
}
// lan mode printer, hide upgrade avaliable state
if (m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingAvaliable &&
m_owner->is_lan_mode_printer()) {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingUnavaliable;
}
}
} else {
// BBS compatibility with old version
if (m_upgrade_status == "DOWNLOADING" ||
m_upgrade_status == "FLASHING" ||
m_upgrade_status == "UPGRADE_REQUEST" ||
m_upgrade_status == "PRE_FLASH_START" ||
m_upgrade_status == "PRE_FLASH_SUCCESS") {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingInProgress;
} else if (m_upgrade_status == "UPGRADE_SUCCESS" ||
m_upgrade_status == "DOWNLOAD_FAIL" ||
m_upgrade_status == "FLASH_FAIL" ||
m_upgrade_status == "PRE_FLASH_FAIL" ||
m_upgrade_status == "UPGRADE_FAIL") {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingFinished;
} else {
if (HasNewVersion()) {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingAvaliable;
} else {
m_upgrade_display_state = DevFirmwareUpgradeState::UpgradingUnavaliable;
}
}
}
}
#define UpgradeNoError 0
#define UpgradeDownloadFailed -1
#define UpgradeVerfifyFailed -2
#define UpgradeFlashFailed -3
#define UpgradePrinting -4
wxString DevUpgrade::GetUpgradeErrCodeStr() const
{
switch (m_upgrade_err_code) {
case UpgradeNoError: return _L("Update successful.");
case UpgradeDownloadFailed: return _L("Downloading failed.");
case UpgradeVerfifyFailed: return _L("Verification failed.");
case UpgradeFlashFailed: return _L("Update failed.");
case UpgradePrinting: return _L("Update failed.");
default: return _L("Update failed.");
}
return "";
}
} // namespace Slic3r
+108
View File
@@ -0,0 +1,108 @@
#pragma once
#include <nlohmann/json.hpp>
#include "slic3r/Utils/json_diff.hpp"
#include "DevDefs.h"
#include "DevFirmware.h"
namespace Slic3r {
class MachineObject;
class DevUpgrade
{
public:
static std::shared_ptr<DevUpgrade> Create(MachineObject *owner) { return std::shared_ptr<DevUpgrade>(new DevUpgrade(owner)); }
public:
// upgrade status
bool IsUpgrading() const { return m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingInProgress; }
bool IsUpgradeAvaliable() const { return m_upgrade_display_state == DevFirmwareUpgradeState::UpgradingAvaliable; }
DevFirmwareUpgradeState GetUpgradeState() const { return m_upgrade_display_state; }
std::string GetUpgradeStatusStr() const { return m_upgrade_status; }
// upgrade request
bool IsUpgradeConsistencyRequest() const { return m_upgrade_consistency_request; }
bool IsUpgradeForceUpgrade() const { return m_upgrade_force_upgrade; }
bool HasNewVersion() const { return m_upgrade_new_version_state == 1; }
std::string GetOtaNewVersion() const { return m_ota_new_version_number; }
std::map<std::string, DevFirmwareVersionInfo> GetNewVersionList() const { return m_new_ver_list; }
// upgrading
std::string GetUpgradeModuleStr() const { return m_upgrade_module; }
std::string GetUpgradeProgressStr() const { return m_upgrade_progress; }
int GetUpgradeProgressInt() const;
std::string GetUpgradeMessageStr() const { return m_upgrade_message; }
wxString GetUpgradeErrCodeStr() const;
public:
// ctrls
int CtrlUpgradeConfirm();
int CtrlUpgradeConsistencyConfirm();
int CtrlUpgradeFirmware(FirmwareInfo info);
int CtrlUpgradeModule(std::string url, std::string module_type, std::string version);
// parser
void ParseUpgrade_V1_0(const json &print_jj);
void ParseUpgradeDisplayState(const json &upgrade_state_jj);
protected:
DevUpgrade(MachineObject *owner) : m_owner(owner) {}
private:
MachineObject *m_owner = nullptr;
int m_upgrade_new_version_state = 0; // 0: invalid version, 1: new version available, 2: no new version
bool m_upgrade_consistency_request = false;
bool m_upgrade_force_upgrade = false;
std::string m_ota_new_version_number;
std::map<std::string, DevFirmwareVersionInfo> m_new_ver_list; // some protcols have version list
DevFirmwareUpgradeState m_upgrade_display_state = DevFirmwareUpgradeState::DC;
int m_upgrade_err_code = 0;
std::string m_upgrade_status;
std::string m_upgrade_progress;
std::string m_upgrade_message;
std::string m_upgrade_module;
};
#if 0 /*TODO*/
class DevUpgradeGetVersionInfo
{
public:
DevUpgradeGetVersionInfo(MachineObject* obj) : m_owner(obj) {} ;
public:
bool IsEmpty() const { m_module_version_map.empty();}
DevFirmwareVersionInfo GetAirPumpVersionInfo() const { return m_air_pump_version_info; }
DevFirmwareVersionInfo GetLaserVersionInfo() const { return m_laser_version_info; }
DevFirmwareVersionInfo GetCuttingModuleVersionInfo() const { return m_cutting_module_version_info; }
DevFirmwareVersionInfo GetExtinguishVersionInfo() const { return m_extinguish_version_info; }
DevFirmwareVersionInfo GetRotaryVersionInfo() const { return m_rotary_version_info; }
std::map<std::string, DevFirmwareVersionInfo> GetModuleVersionInfoMap() const { return m_module_version_map; }
public:
int CtrlGetVersion(bool with_retry = true);
void ParseGetVersion(const json &print_jj);
private:
MachineObject *m_owner;
// some version info
DevFirmwareVersionInfo m_air_pump_version_info;
DevFirmwareVersionInfo m_laser_version_info;
DevFirmwareVersionInfo m_cutting_module_version_info;
DevFirmwareVersionInfo m_extinguish_version_info;
DevFirmwareVersionInfo m_rotary_version_info;
std::map<std::string, DevFirmwareVersionInfo> m_module_version_map;
// ctrl retry
int m_retry_count = 0;
int m_max_retry = 3;
};
#endif
} // namespace Slic3r
@@ -0,0 +1,56 @@
#include "DevUpgrade.h"
#include "DevUtil.h"
#include "slic3r/GUI/DeviceManager.hpp"
namespace Slic3r {
int DevUpgrade::CtrlUpgradeConfirm()
{
json j;
j["upgrade"]["command"] = "upgrade_confirm";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["src_id"] = 1; // 1 for slicer
return m_owner->publish_json(j);
}
int DevUpgrade::CtrlUpgradeConsistencyConfirm()
{
json j;
j["upgrade"]["command"] = "consistency_confirm";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["src_id"] = 1; // 1 for slicer
return m_owner->publish_json(j);
}
int DevUpgrade::CtrlUpgradeFirmware(FirmwareInfo info)
{
std::string version = info.version;
std::string dst_url = info.url;
std::string module_name = info.module_type;
json j;
j["upgrade"]["command"] = "start";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["url"] = info.url;
j["upgrade"]["module"] = info.module_type;
j["upgrade"]["version"] = info.version;
j["upgrade"]["src_id"] = 1;
return m_owner->publish_json(j);
}
int DevUpgrade::CtrlUpgradeModule(std::string url, std::string module_type, std::string version)
{
json j;
j["upgrade"]["command"] = "start";
j["upgrade"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["upgrade"]["url"] = url;
j["upgrade"]["module"] = module_type;
j["upgrade"]["version"] = version;
j["upgrade"]["src_id"] = 1;
return m_owner->publish_json(j);
}
} // namespace Slic3r
+73
View File
@@ -24,6 +24,79 @@ int DevUtil::get_flag_bits(std::string str, int start, int count)
return 0;
}
uint32_t DevUtil::get_flag_bits_no_border(std::string str, int start_idx, int count)
{
if (start_idx < 0 || count <= 0) return 0;
try {
// --- 1) trim ---
auto ltrim = [](std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); })); };
auto rtrim = [](std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end()); };
ltrim(str);
rtrim(str);
// --- 2) remove 0x/0X prefix ---
if (str.size() >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { str.erase(0, 2); }
// --- 3) keep only hex digits ---
std::string hex;
hex.reserve(str.size());
for (char c : str) {
if (std::isxdigit(static_cast<unsigned char>(c))) hex.push_back(c);
}
if (hex.empty()) return 0;
// --- 4) use size_t for all index/bit math ---
const size_t total_bits = hex.size() * 4ULL;
const size_t ustart = static_cast<size_t>(start_idx);
if (ustart >= total_bits) return 0;
const int int_bits = std::numeric_limits<uint32_t>::digits; // typically 32
const size_t need_bits = static_cast<size_t>(std::min(count, int_bits));
// [first_bit, last_bit]
const size_t first_bit = ustart;
const size_t last_bit = std::min(ustart + need_bits, total_bits) - 1ULL;
if (last_bit < first_bit) return 0;
const size_t right_index = hex.size() - 1ULL;
const size_t first_nibble = first_bit / 4ULL;
const size_t last_nibble = last_bit / 4ULL;
const size_t start_idx = right_index - last_nibble;
const size_t end_idx = right_index - first_nibble;
if (end_idx < start_idx) return 0;
const size_t sub_len = end_idx - start_idx + 1ULL;
if (end_idx >= hex.size()) return 0;
const std::string sub_hex = hex.substr(start_idx, sub_len);
unsigned long long chunk = std::stoull(sub_hex, nullptr, 16);
const unsigned nibble_offset = static_cast<unsigned>(first_bit % 4ULL);
const unsigned long long shifted = (nibble_offset == 0U) ? chunk : (chunk >> nibble_offset);
uint32_t mask;
if (need_bits >= static_cast<size_t>(std::numeric_limits<uint32_t>::digits)) {
mask = std::numeric_limits<uint32_t>::max();
} else {
mask = static_cast<uint32_t>((1ULL << need_bits) - 1ULL);
}
const uint32_t val = static_cast<uint32_t>(shifted & mask);
return val;
} catch (const std::invalid_argument &) {
return 0;
} catch (const std::out_of_range &) {
return 0;
} catch (...) {
return 0;
}
}
int DevUtil::get_flag_bits(int num, int start, int count, int base)
{
+103 -2
View File
@@ -12,8 +12,15 @@
#include <stdexcept>
#include <boost/log/trivial.hpp>
#include <boost/lexical_cast.hpp>
#include "nlohmann/json.hpp"
/* Sequence Id*/
#define STUDIO_START_SEQ_ID 20000
#define STUDIO_END_SEQ_ID 30000
#define CLOUD_SEQ_ID 0
namespace Slic3r
{
@@ -27,14 +34,18 @@ public:
public:
static int get_flag_bits(std::string str, int start, int count = 1);
static int get_flag_bits(int num, int start, int count = 1, int base = 10);
static uint32_t get_flag_bits_no_border(std::string str, int start_idx, int count = 1);
// Extract the hex digit at position `pos` (0 = least-significant).
// eg. get_hex_bits(16, 1, 10) = 1
static int get_hex_bits(int num, int pos, int input_num_base = 10) { return get_flag_bits(num, pos * 4, 4, input_num_base); }
static int get_hex_bits(int num, int pos, int input_num_base = 10) { return get_flag_bits(num, pos * 4, 4, input_num_base);};
static float string_to_float(const std::string& str_value);
static std::string convertToIp(long long ip);
// sequence id check
static bool is_studio_cmd(int seq) { return seq >= STUDIO_START_SEQ_ID && seq < STUDIO_END_SEQ_ID;};
static bool is_cloud_cmd(int seq) { return seq == CLOUD_SEQ_ID;};
};
@@ -108,4 +119,94 @@ struct NumericStrCompare
}
};
enum class DirtyMode{
COUNTER,
TIMER
};
template<typename T>
class DevDirtyHandler{
public:
DevDirtyHandler(T init_value, int setting_threshold, DirtyMode mode): m_value(init_value), m_setting_threshold(setting_threshold), m_mode(mode)
{
m_threshold = setting_threshold;
}
~DevDirtyHandler(){};
T GetValue() const { return m_value; };
void SetOptimisticValue(const T& data)
{
m_value = data;
m_start_time = time(nullptr);
m_threshold = m_setting_threshold;
}
void UpdateValue(const T& data)
{
if (m_mode == DirtyMode::COUNTER)
{
if (m_threshold > 0)
m_threshold--;
else
m_value = data;
}
else if (m_mode == DirtyMode::TIMER)
{
if (time(nullptr) - m_start_time > m_threshold)
m_value = data;
}
}
private:
T m_value;
DirtyMode m_mode;
int m_setting_threshold{0};
int m_start_time{0};
int m_threshold{0};
};
static std::string s_get_diameter_str(float diameter)
{
return (boost::format("%.2f") % diameter).str();
}
static std::string s_get_diameter_str(const std::string& diameter)
{
try {
float dia = boost::lexical_cast<float>(diameter);
return s_get_diameter_str(dia);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to boost::lexical_cast: " << diameter;
return diameter;
}
try {
float dia = std::stof(diameter);
return s_get_diameter_str(dia);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " std::stof: " << diameter;
return diameter;
}
}
static float s_get_diameter(const std::string& diameter)
{
try {
return boost::lexical_cast<float>(diameter);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to boost::lexical_cast: " << diameter;
}
try {
return std::stof(diameter);
} catch (...) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " std::stof: " << diameter;
}
return 0.0;
}
}; // namespace Slic3r
+42 -5
View File
@@ -2,15 +2,28 @@
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
#include "slic3r/GUI/PartPlate.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include <boost/log/trivial.hpp>
#include <boost/lexical_cast.hpp>
#include <wx/string.h>
namespace Slic3r
{
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> DevUtilBackend::GetNozzleGroupResult(Slic3r::GUI::Plater* plater)
Slic3r::MultiNozzleUtils::NozzleInfo DevUtilBackend::GetNozzleInfo(const DevNozzle& dev_nozzle)
{
MultiNozzleUtils::NozzleInfo info;
info.diameter = dev_nozzle.GetNozzleDiameterStr().ToStdString();
info.volume_type = DevNozzle::ToNozzleVolumeType(dev_nozzle.GetNozzleFlowType());
info.extruder_id = dev_nozzle.GetLogicExtruderId();
return info;
}
std::shared_ptr<Slic3r::MultiNozzleUtils::NozzleGroupResultBase> DevUtilBackend::GetNozzleGroupResult(Slic3r::GUI::Plater *plater)
{
if (plater && plater->background_process().get_current_gcode_result()) {
return plater->background_process().get_current_gcode_result()->nozzle_group_result;
@@ -19,6 +32,29 @@ std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> DevUtilBackend::GetNozz
return nullptr;
}
std::unordered_map<NozzleDef, int> DevUtilBackend::CollectNozzleInfo(MultiNozzleUtils::NozzleGroupResultBase *nozzle_group_res, int logic_ext_id)
{
std::unordered_map<NozzleDef, int> need_nozzle_map;
if (!nozzle_group_res) {
return need_nozzle_map;
}
const std::vector<Slic3r::MultiNozzleUtils::NozzleInfo>& nozzle_vec = nozzle_group_res->get_used_nozzles_in_extruder(logic_ext_id);
for (auto slicing_nozzle : nozzle_vec) {
try {
NozzleDef data;
data.nozzle_diameter = boost::lexical_cast<float>(slicing_nozzle.diameter);
data.nozzle_flow_type = DevNozzle::ToNozzleFlowType(slicing_nozzle.volume_type);
need_nozzle_map[data]++;
} catch (const std::exception& e) {
assert(0);
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "exception: " << e.what();
}
}
return need_nozzle_map;
}
static std::unordered_map<std::string, DevAmsType> s_ams_type_map = {
{"0", DevAmsType::N3F},
{"1", DevAmsType::N3S},
@@ -41,6 +77,7 @@ std::optional<Slic3r::DevFilamentDryingPreset> DevUtilBackend::GetFilamentDrying
std::vector<std::string> types = config.option<ConfigOptionStrings>("filament_dev_ams_drying_ams_limitations")->values;
for (auto type : types) {
if (s_ams_type_map.count(type) == 0) {
// assert(0);
continue;
}
info.ams_limitations.insert(s_ams_type_map[type]);
@@ -62,10 +99,10 @@ std::optional<Slic3r::DevFilamentDryingPreset> DevUtilBackend::GetFilamentDrying
}
if (config.has("filament_dev_drying_softening_temperature")) {
info.filament_dev_drying_softening_temperature = config.option<ConfigOptionFloats>("filament_dev_drying_softening_temperature")->get_at(0);
info.filament_dev_drying_softening_temperature = config.option<ConfigOptionFloats>("filament_dev_drying_softening_temperature")->get_at(0);
}
if (config.has("filament_dev_ams_drying_heat_distortion_temperature")) {
if (config.has("filament_dev_ams_drying_heat_distortion_temperature")){
info.filament_dev_ams_drying_heat_distortion_temperature = config.option<ConfigOptionFloats>("filament_dev_ams_drying_heat_distortion_temperature")->get_at(0);
}
@@ -83,4 +120,4 @@ std::optional<Slic3r::DevFilamentDryingPreset> DevUtilBackend::GetFilamentDrying
return std::nullopt;
}
}; // namespace Slic3r
};// namespace Slic3r
+21 -8
View File
@@ -1,19 +1,32 @@
/**
* @file DevUtilBackend.h
* @brief Provides common static utility methods for backend (preset/slicing).
*
* This class offers a collection of static helper functions such as string manipulation,
* file operations, and other frequently used utilities.
*/
#pragma once
#include "DevDefs.h"
#include "DevNozzleSystem.h"
#include "DevFilaSystem.h"
#include "libslic3r/MultiNozzleUtils.hpp"
#include <optional>
#include <string>
#include <unordered_map>
// Forward declarations
namespace Slic3r
{
class MachineObject;
namespace GUI
{
class Plater;
}
}; // namespace Slic3r::GUI
namespace Slic3r
{
namespace GUI { class Plater; }
class DevUtilBackend
{
@@ -21,14 +34,14 @@ public:
DevUtilBackend() = delete;
public:
static MultiNozzleUtils::NozzleInfo GetNozzleInfo(const DevNozzle& dev_nozzle);
// for rack: the slicer's per-filament -> logical-nozzle grouping for the current plate, read off
// the post-slice GCodeProcessorResult (plater->background_process().get_current_gcode_result()).
// Returns nullptr when there is no plater / no current result.
static std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> GetNozzleGroupResult(Slic3r::GUI::Plater* plater);
// for rack
static std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> GetNozzleGroupResult(Slic3r::GUI::Plater *plater);
static std::unordered_map<NozzleDef, int> CollectNozzleInfo(MultiNozzleUtils::NozzleGroupResultBase *nozzle_group_res, int logic_ext_id);
// for filament preset
static std::optional<DevFilamentDryingPreset> GetFilamentDryingPreset(const std::string& fila_id);
};
}; // namespace Slic3r
}; // namespace Slic3r
+190 -17
View File
@@ -5,6 +5,10 @@
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "ReleaseNote.hpp"
#include "wxExtensions.hpp"
#include <wx/mstream.h>
#include <wx/dcmemory.h>
namespace Slic3r {
namespace GUI
@@ -88,10 +92,22 @@ DeviceErrorDialog::DeviceErrorDialog(MachineObject* obj, wxWindow* parent, wxWin
if (m_obj) { m_obj->command_clean_print_error_uiop(m_obj->print_error); }
e.Skip();
});
m_request_timer = new wxTimer(this);
Bind(wxEVT_TIMER, &DeviceErrorDialog::on_request_timeout, this, m_request_timer->GetId());
}
DeviceErrorDialog::~DeviceErrorDialog()
{
// Orca: invalidate any in-flight cloud snapshot callback (the request has no cancel handle)
*m_alive = false;
if (m_request_timer) {
m_request_timer->Stop();
delete m_request_timer;
m_request_timer = nullptr;
}
if (web_request.IsOk() && web_request.GetState() == wxWebRequest::State_Active)
{
BOOST_LOG_TRIVIAL(info) << "web_request: cancelled";
@@ -103,6 +119,11 @@ DeviceErrorDialog::~DeviceErrorDialog()
void DeviceErrorDialog::on_webrequest_state(wxWebRequestEvent& evt)
{
BOOST_LOG_TRIVIAL(trace) << "monitor: monitor_panel web request state = " << evt.GetState();
// A local/HTTP illustration resolved (or the cloud path fell back to it): stop the watchdog.
m_request_cancelled.store(true);
clear_request_timer();
switch (evt.GetState())
{
case wxWebRequest::State_Completed:
@@ -120,7 +141,9 @@ void DeviceErrorDialog::on_webrequest_state(wxWebRequestEvent& evt)
case wxWebRequest::State_Cancelled:
case wxWebRequest::State_Unauthorized:
{
m_error_picture->SetBitmap(wxBitmap());
m_error_picture->SetBitmap(make_placeholder_image(_L("Network unavailable")));
Layout();
Fit();
break;
}
case wxWebRequest::State_Active:
@@ -129,6 +152,123 @@ void DeviceErrorDialog::on_webrequest_state(wxWebRequestEvent& evt)
}
}
void DeviceErrorDialog::on_request_timeout(wxTimerEvent& event)
{
if (m_request_cancelled.load()) { return; }
// Same fallback order as the cloud callback: local/HTTP illustration first, placeholder last.
if (!get_fail_snapshot_from_local(m_local_img_url)) {
m_error_picture->SetBitmap(make_placeholder_image(_L("Network unavailable")));
}
Layout();
Fit();
}
void DeviceErrorDialog::clear_request_timer()
{
if (m_request_timer && m_request_timer->IsRunning()) {
m_request_timer->Stop();
}
}
wxBitmap DeviceErrorDialog::make_placeholder_image(const wxString& text)
{
const int w = FromDIP(320);
const int h = FromDIP(180);
wxBitmap bmp(wxSize(w, h));
wxMemoryDC dc(bmp);
dc.SetBackground(wxBrush(wxColour(238, 238, 238)));
dc.Clear();
ScalableBitmap icon = ScalableBitmap(this, "dev_hms_diag_loading", 80);
wxBitmap icon_bmp = icon.bmp();
if (icon_bmp.IsOk()) {
int ix = (w - icon_bmp.GetWidth()) / 2;
int iy = (h - icon_bmp.GetHeight()) / 3;
dc.DrawBitmap(icon_bmp, ix, iy, true);
}
dc.SetTextForeground(wxColour(158, 158, 158));
dc.SetFont(Label::Body_14);
wxSize txtSize = dc.GetTextExtent(text);
int tx = (w - txtSize.GetWidth()) / 2;
int ty = h - txtSize.GetHeight() - FromDIP(45);
dc.DrawText(text, tx, ty);
dc.SelectObject(wxNullBitmap);
return bmp;
}
bool DeviceErrorDialog::get_fail_snapshot_from_cloud()
{
if (!m_obj || m_obj->m_print_error_img_id.empty()) { return false; }
NetworkAgent* agent = wxGetApp().getAgent();
if (!agent) { return false; }
// Orca: the cloud request has no cancel handle, so guard the callback against dialog
// destruction with a liveness token (m_alive, cleared in the dtor) and marshal onto the UI
// thread via the always-valid app handler instead of this->CallAfter. The raw bytes are
// decoded on the UI thread so no wxImage (non-atomic refcount) is shared across threads.
// m_request_seq guards against a callback from a previous error painting over the
// current one when the dialog is reused by a later show_error_code().
auto alive = m_alive;
const int seq = m_request_seq;
int ret = agent->get_hms_snapshot(m_obj->get_dev_id(), m_obj->m_print_error_img_id,
[this, alive, seq](std::string body, int status) {
if (!alive->load()) { return; }
wxGetApp().CallAfter([this, alive, seq, body = std::move(body), status]() {
if (!alive->load()) { return; }
if (seq != m_request_seq) { return; }
m_request_cancelled.store(true);
clear_request_timer();
wxImage image;
bool ok = false;
if (status == 200) {
wxMemoryInputStream stream(body.data(), body.size());
ok = image.LoadFile(stream, wxBITMAP_TYPE_ANY);
if (!ok) { BOOST_LOG_TRIVIAL(error) << "get_fail_snapshot_from_cloud: failed to resolve stream"; }
} else {
BOOST_LOG_TRIVIAL(error) << "get_fail_snapshot_from_cloud: status = " << status;
}
if (ok) {
wxImage resize_img = image.Scale(FromDIP(320), FromDIP(180), wxIMAGE_QUALITY_HIGH);
m_error_picture->SetBitmap(wxBitmap(resize_img));
} else if (!get_fail_snapshot_from_local(m_local_img_url)) {
m_error_picture->SetBitmap(make_placeholder_image(_L("Network unavailable")));
}
Layout();
Fit();
});
});
return ret == 0; // dispatched; the frame arrives later via the callback
}
bool DeviceErrorDialog::get_fail_snapshot_from_local(const wxString& image_url)
{
if (image_url.empty()) { return false; }
const wxImage& img = wxGetApp().get_hms_query()->query_image_from_local(image_url);
if (!img.IsOk() && image_url.Contains("http"))
{
web_request = wxWebSession::GetDefault().CreateRequest(this, image_url);
BOOST_LOG_TRIVIAL(trace) << "monitor: create new webrequest, state = " << web_request.GetState();
if (web_request.GetState() == wxWebRequest::State_Idle) web_request.Start();
BOOST_LOG_TRIVIAL(trace) << "monitor: start new webrequest, state = " << web_request.GetState();
}
else
{
m_request_cancelled.store(true);
clear_request_timer();
const wxImage& resize_img = img.Scale(FromDIP(320), FromDIP(180), wxIMAGE_QUALITY_HIGH);
m_error_picture->SetBitmap(wxBitmap(resize_img));
}
return true;
}
void DeviceErrorDialog::init_button(ActionButton style, wxString buton_text)
{
if (btn_bg_white.count() == 0)
@@ -170,6 +310,7 @@ void DeviceErrorDialog::init_button_list()
init_button(FILAMENT_LOAD_RESUME, _L("Filament Loaded, Resume"));
init_button(JUMP_TO_LIVEVIEW, _L("View Liveview"));
init_button(NO_REMINDER_NEXT_TIME, _L("No Reminder Next Time"));
init_button(REFRESH_NOZZLE, _L("Recheck"));
init_button(IGNORE_NO_REMINDER_NEXT_TIME, _L("Ignore. Don't Remind Next Time"));
init_button(IGNORE_RESUME, _L("Ignore this and Resume"));
init_button(PROBLEM_SOLVED_RESUME, _L("Problem Solved and Resume"));
@@ -178,6 +319,10 @@ void DeviceErrorDialog::init_button_list()
init_button(CANCEL, _L("Cancel"));
init_button(STOP_DRYING, _L("Stop Drying"));
init_button(PROCEED, _L("Proceed"));
init_button(OK_JUMP_RACK, _L("OK"));
init_button(ABORT, _L("Abort"));
init_button(DISABLE_PURIFICATION, _L("Disable Purification for This Print"));
init_button(DONT_REMIND_NEXT_TIME, _L("Don't Remind Me"));
init_button(DBL_CHECK_CANCEL, _L("Cancel"));
init_button(DBL_CHECK_DONE, _L("Done"));
init_button(DBL_CHECK_RETRY, _L("Retry"));
@@ -241,6 +386,7 @@ wxString DeviceErrorDialog::show_error_code(int error_code)
Raise();
#ifndef __linux__
// Orca: skip RequestUserAttention on Linux/Wayland (urgency-hint deadlock).
// On Linux (especially Wayland) RequestUserAttention(wxUSER_ATTENTION_ERROR) maps to
// gtk_window_set_urgency_hint(TRUE) which can leave the window in an urgent-but-unfocused
// state — clicks no longer reach any widget in the app and the user has to kill the
@@ -305,22 +451,23 @@ void DeviceErrorDialog::update_contents(const wxString& title, const wxString& t
}
/* image */
if (!image_url.empty())
// Orca: tiered image slot — prefer the printer's captured camera frame of the failure
// (cloud, keyed by m_print_error_img_id), else the local/HTTP HMS illustration, else hide.
// Reuses m_error_picture + on_webrequest_state (single widget, no second request). The
// loading placeholder and 10s watchdog are armed only for the async cloud fetch; the local
// path keeps its existing behavior.
m_local_img_url = image_url;
m_request_cancelled.store(false);
++m_request_seq; // invalidate any snapshot fetch still in flight for the previous error
clear_request_timer();
if (get_fail_snapshot_from_cloud())
{
m_error_picture->SetBitmap(make_placeholder_image(_L("Loading ...")));
m_request_timer->StartOnce(10000);
m_error_picture->Show();
}
else if (get_fail_snapshot_from_local(image_url))
{
const wxImage& img = wxGetApp().get_hms_query()->query_image_from_local(image_url);
if (!img.IsOk() && image_url.Contains("http"))
{
web_request = wxWebSession::GetDefault().CreateRequest(this, image_url);
BOOST_LOG_TRIVIAL(trace) << "monitor: create new webrequest, state = " << web_request.GetState();
if (web_request.GetState() == wxWebRequest::State_Idle) web_request.Start();
BOOST_LOG_TRIVIAL(trace) << "monitor: start new webrequest, state = " << web_request.GetState();
}
else
{
const wxImage& resize_img = img.Scale(FromDIP(320), FromDIP(180), wxIMAGE_QUALITY_HIGH);
m_error_picture->SetBitmap(wxBitmap(resize_img));
}
m_error_picture->Show();
}
else
@@ -349,7 +496,10 @@ void DeviceErrorDialog::update_contents(const wxString& title, const wxString& t
auto text_size = m_error_msg_label->GetBestSize();
if (text_size.y < FromDIP(360))
{
if (!image_url.empty())
// Orca: reserve the image area exactly when an image is coming (cloud snapshot,
// local illustration, or the loading placeholder) - m_error_picture's visibility
// was decided above, so no blank slot is reserved when neither source is usable.
if (m_error_picture->IsShown())
{
m_scroll_area->SetMinSize(wxSize(FromDIP(320), text_size.y + FromDIP(220)));
}
@@ -462,6 +612,29 @@ void DeviceErrorDialog::on_button_click(ActionButton btn_id)
}
break;
}
case DeviceErrorDialog::REFRESH_NOZZLE: {
m_obj->command_refresh_nozzle();
break;
}
case DeviceErrorDialog::OK_JUMP_RACK: {
Slic3r::GUI::wxGetApp().mainframe->jump_to_monitor();
Slic3r::GUI::wxGetApp().mainframe->m_monitor->jump_to_Rack();
break;
}
case DeviceErrorDialog::ABORT: {
m_obj->command_ams_control("abort");
break;
}
case DeviceErrorDialog::DISABLE_PURIFICATION: {
m_obj->command_purification_disable();
break;
}
case DeviceErrorDialog::DONT_REMIND_NEXT_TIME: {
if (!m_action_json.is_null()) {
m_obj->command_dont_remind_next_time(m_action_json);
}
break;
}
case DeviceErrorDialog::ERROR_BUTTON_COUNT: break;
case DeviceErrorDialog::DBL_CHECK_CANCEL: {
+21
View File
@@ -1,7 +1,10 @@
#pragma once
#include <unordered_set>
#include <atomic>
#include <memory>
#include <wx/statbmp.h>
#include <wx/timer.h>
#include <wx/webrequest.h>
#include "GUI_Utils.hpp"
@@ -35,6 +38,7 @@ public:
JUMP_TO_LIVEVIEW,
NO_REMINDER_NEXT_TIME = 23,
REFRESH_NOZZLE = 24,
IGNORE_NO_REMINDER_NEXT_TIME = 25,
//LOAD_FILAMENT = 26*/
IGNORE_RESUME = 27,
@@ -46,6 +50,10 @@ public:
CANCEL = 37,
REMOVE_CLOSE_BTN = 39, // special case, do not show close button
PROCEED = 41,
OK_JUMP_RACK = 49,
ABORT = 51,
DISABLE_PURIFICATION = 54,
DONT_REMIND_NEXT_TIME = 57,
ERROR_BUTTON_COUNT,
@@ -84,6 +92,11 @@ protected:
void on_button_click(ActionButton btn_id);
void on_webrequest_state(wxWebRequestEvent& evt);
void on_request_timeout(wxTimerEvent& event);
void clear_request_timer();
wxBitmap make_placeholder_image(const wxString& text);
bool get_fail_snapshot_from_cloud();
bool get_fail_snapshot_from_local(const wxString& image_url);
void on_dpi_changed(const wxRect& suggested_rect);
private:
@@ -93,6 +106,14 @@ private:
std::unordered_set<Button*> m_used_button;
wxWebRequest web_request;
wxTimer* m_request_timer{ nullptr };
std::atomic<bool> m_request_cancelled{ false };
// UI-thread-only generation counter: bumped on every update_contents() so a cloud
// snapshot callback from a previous error cannot paint over the current one.
int m_request_seq{ 0 };
wxString m_local_img_url;
// Orca: liveness token for the async cloud snapshot callback (the request has no cancel handle)
std::shared_ptr<std::atomic_bool> m_alive{ std::make_shared<std::atomic_bool>(true) };
wxStaticBitmap* m_error_picture;
Label* m_error_msg_label{ nullptr };
Label* m_error_code_label{ nullptr };
+250 -3
View File
@@ -44,6 +44,12 @@
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevUtil.h"
// Orca: adopt DeviceCore split — axis/calib/chamber/status/upgrade modules
#include "DeviceCore/DevAxis.h"
#include "DeviceCore/DevChamber.h"
#include "DeviceCore/DevStatus.h"
#include "DeviceCore/DevUpgrade.h"
#define CALI_DEBUG
#define MINUTE_30 1800000 //ms
@@ -68,6 +74,44 @@ int get_tray_id_by_ams_id_and_slot_id(int ams_id, int slot_id)
}
}
namespace {
// Stringing-prone filament IDs per nozzle-diameter bucket.
// Mirrors the printer firmware tables (see g_leak_pron_idx_for_0_4 / _0_6_0_8).
// Keep these in sync with firmware when new stringing-prone filaments are added.
const std::unordered_set<std::string> g_stringing_prone_for_0_4 = {
"GFA11", // PLA Aero
"GFU90", // TPU 90A
"GFU00", // TPU 95A HF
"GFU02", // Generic TPU for AMS
"GFU98", // TPU for AMS
};
const std::unordered_set<std::string> g_stringing_prone_for_0_6_0_8 = {
"GFA11", // PLA Aero
"GFU00", // TPU 95A HF
};
// Pick the right table for the given nozzle diameter; returns nullptr if the
// nozzle bucket has no entries (e.g. 0.2 mm) or the diameter is invalid.
const std::unordered_set<std::string>* pick_stringing_set(float nozzle_diameter)
{
if (!(nozzle_diameter > 0.f)) return nullptr;
if (nozzle_diameter < 0.3f) return nullptr; // 0.2 nozzle: empty
if (nozzle_diameter < 0.5f) return &g_stringing_prone_for_0_4;
return &g_stringing_prone_for_0_6_0_8; // 0.6 / 0.8 nozzles
}
} // namespace
bool Slic3r::is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter)
{
if (filament_id.empty()) return false;
const auto* set = pick_stringing_set(nozzle_diameter);
if (!set) return false;
return set->count(filament_id) > 0;
}
wxString Slic3r::get_stage_string(int stage)
{
switch(stage) {
@@ -553,6 +597,7 @@ MachineObject::MachineObject(DeviceManager* manager, NetworkAgent* agent, std::s
mc_print_sub_stage = 0;
mc_left_time = 0;
hw_switch_state = 0;
m_print_error_img_id = "";
has_ipcam = true; // default true
@@ -577,6 +622,13 @@ MachineObject::MachineObject(DeviceManager* manager, NetworkAgent* agent, std::s
m_print_options = new DevPrintOptions(this);
m_nozzle_mapping_ptr = std::make_shared<DevNozzleMappingCtrl>(this);
// Orca: adopt DeviceCore split — axis/calib/chamber/status/upgrade modules
m_axis = DevAxis::Create(this);
m_chamber = DevChamber::Create(this);
m_upgrade = DevUpgrade::Create(this);
m_status = new DevStatus(this);
m_calib = new DevCalib(this);
}
}
@@ -629,6 +681,13 @@ MachineObject::~MachineObject()
delete m_print_options;
m_print_options = nullptr;
// Orca: adopt DeviceCore split
delete m_calib;
m_calib = nullptr;
delete m_status;
m_status = nullptr;
}
}
@@ -698,6 +757,33 @@ std::string MachineObject::get_filament_display_type(const std::string& ams_id,
return this->get_tray(ams_id, tray_id).get_display_filament_type();
}
bool MachineObject::any_loaded_filament_is_stringing_prone() const
{
if (print_job_filament_mapping.empty()) return false;
std::vector<float> nozzle_diameters;
if (m_extder_system) {
for (const auto& ext : m_extder_system->GetExtruders()) {
const float d = ext.GetNozzleDiameter();
if (d > 0.f) nozzle_diameters.push_back(d);
}
}
if (nozzle_diameters.empty()) return false;
for (uint16_t v : print_job_filament_mapping) {
if (v == 0xFFFF) continue;
const int ams_id = (v >> 8) & 0xFF;
const int slot_id = v & 0xFF;
const std::string fid = this->get_filament_id(std::to_string(ams_id), std::to_string(slot_id));
if (fid.empty()) continue;
for (float d : nozzle_diameters) {
if (Slic3r::is_stringing_prone_filament(fid, d))
return true;
}
}
return false;
}
void MachineObject::_parse_ams_status(int ams_status)
{
ams_status_sub = ams_status & 0xFF;
@@ -716,6 +802,8 @@ void MachineObject::_parse_ams_status(int ams_status)
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_SELF_CHECK;
} else if (ams_status_main_int == (int) AmsStatusMain::AMS_STATUS_MAIN_DEBUG) {
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_DEBUG;
} else if (ams_status_main_int == (int) AmsStatusMain::AMS_STATUS_MAIN_COLD_PULL) {
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_COLD_PULL;
} else {
ams_status_main = AmsStatusMain::AMS_STATUS_MAIN_UNKNOWN;
}
@@ -867,6 +955,9 @@ void MachineObject::clear_version_info()
laser_version_info = DevFirmwareVersionInfo();
cutting_module_version_info = DevFirmwareVersionInfo();
extinguish_version_info = DevFirmwareVersionInfo();
rotary_version_info = DevFirmwareVersionInfo();
exhaustfan_version_info = DevFirmwareVersionInfo();
amshub_version_info = DevFirmwareVersionInfo();
filatrack_version_info = DevFirmwareVersionInfo();
module_vers.clear();
// Drop cached rack-hotend (WTM) firmware alongside the module list.
@@ -884,11 +975,17 @@ void MachineObject::store_version_info(const DevFirmwareVersionInfo& info)
cutting_module_version_info = info;
} else if (info.isExtinguishSystem()) {
extinguish_version_info = info;
} else if (info.isRotary()) {
rotary_version_info = info;
} else if (info.isWTM()) {
// Route rack-hotend / extruder-nozzle firmware into the nozzle system so
// the rack upgrade UI can read per-nozzle versions. isWTM() is false for every non-rack
// printer's modules, so this branch never fires outside H2C.
m_nozzle_system->AddFirmwareInfoWTM(info);
} else if (info.isExhaustFan()) {
exhaustfan_version_info = info;
} else if (info.isHmshub()) {
amshub_version_info = info;
} else if (info.isFilaTrackSwitch()) {
filatrack_version_info = info;
}
@@ -1207,7 +1304,7 @@ bool MachineObject::canEnableTimelapse(wxString &error_message) const
return true;
}
if (m_storage->get_sdcard_state() != DevStorage::SdcardState::HAS_SDCARD_NORMAL) {
if (m_storage->get_sdcard_state() != DevStorage::SdcardState::HAS_SDCARD_NORMAL && !m_has_timelapse_kit) {
if (m_storage->get_sdcard_state() == DevStorage::SdcardState::NO_SDCARD) {
error_message = _L("Timelapse is not supported while the storage does not exist.");
} else if (m_storage->get_sdcard_state() == DevStorage::SdcardState::HAS_SDCARD_ABNORMAL) {
@@ -1857,10 +1954,17 @@ int MachineObject::command_axis_control(std::string axis, double unit, double in
{
if (m_support_mqtt_axis_control)
{
int dir = input_val > 0 ? 1 : -1;
// i3-arch printers move the bed for Y/Z, so the on-screen direction is
// reversed — same negation the g-code fallback below applies.
if (!is_core_xy() && (axis.compare("Y") == 0 || axis.compare("Z") == 0)) {
dir = -dir;
}
json j;
j["print"]["command"] = "xyz_ctrl";
j["print"]["axis"] = axis;
j["print"]["dir"] = input_val > 0 ? 1 : -1;
j["print"]["dir"] = dir;
j["print"]["mode"] = (std::abs(input_val) >= 10) ? 1 : 0;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j);
@@ -2199,6 +2303,14 @@ int MachineObject::command_ack_proceed(json& proceed) {
} else {
proceed["err_ignored"] = std::vector<int>{proceed["err_index"]};
}
for (auto& item : proceed["err_ignored"]) {
json error_item;
error_item["idx"] = item.get<int>();
error_item["mode"] = 0;
proceed["rm_idx"].push_back(error_item);
}
proceed["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
json j;
@@ -2206,6 +2318,78 @@ int MachineObject::command_ack_proceed(json& proceed) {
return this->publish_json(j);
}
bool MachineObject::is_timelapse_storage_low(const std::string& storage) const
{
return m_storage && m_storage->is_timelapse_storage_low(storage);
}
int MachineObject::command_ipcam_check_timelapse_storage(const std::string& storage, int total_layer)
{
json j;
j["camera"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["camera"]["command"] = "ipcam_get_media_info";
j["camera"]["sub_command"] = "is_timelapse_storage_enough";
j["camera"]["storage"] = storage;
j["camera"]["total_layer"] = total_layer;
return this->publish_json(j);
}
int MachineObject::command_ipcam_delete_oldest_timelapse(const std::string& storage, int total_layer)
{
json j;
j["camera"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
j["camera"]["command"] = "ipcam_delete_oldest_timelapse";
j["camera"]["storage"] = storage;
j["camera"]["total_layer"] = total_layer;
return this->publish_json(j);
}
int MachineObject::command_purification_disable()
{
json j;
j["print"]["command"] = "close_air_filt";
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j, 1);
}
int MachineObject::command_dont_remind_next_time(json& mqtt_guard_json)
{
if (!mqtt_guard_json.contains("command") ||
!mqtt_guard_json.contains("err_index") ||
mqtt_guard_json["err_index"].empty()) return -1;
json j;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
try {
j["print"]["command"] = mqtt_guard_json["command"].get<std::string>();
int err_index = mqtt_guard_json["err_index"].get<int>();
if (mqtt_guard_json.contains("err_ignored") &&
mqtt_guard_json["err_ignored"].is_array()) {
j["print"]["err_ignored"] = mqtt_guard_json["err_ignored"];
j["print"]["err_ignored"].push_back(err_index);
} else {
j["print"]["err_ignored"] = std::vector<int>{err_index};
}
for (auto& item : j["print"]["err_ignored"]) {
if (!item.is_number_integer()) continue;
json item_json;
item_json["idx"] = item.get<int>();
item_json["mode"] = 1; // 1-next time ignore, 2-always ignore
j["print"]["rm_idx"].push_back(item_json);
}
} catch (const json::exception& e) {
BOOST_LOG_TRIVIAL(error) << "JSON parsing error in command_dont_remind_next_time: " << e.what();
return -1;
}
return this->publish_json(j, 1);
}
int MachineObject::command_xcam_control_ai_monitoring(bool on_off, std::string lvl)
{
bool print_halt = (lvl == "never_halt") ? false:true;
@@ -2915,6 +3099,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
//supported function
m_config->ParseConfig(jj);
m_status->ParseStatus(jj); // Orca: adopt DeviceCore split — populate DevStatus module
if (jj.contains("support_build_plate_marker_detect")) {
if (jj["support_build_plate_marker_detect"].is_boolean()) {
@@ -3142,6 +3327,17 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
if (jj["print_error"].is_number())
print_error = jj["print_error"].get<int>();
}
// Orca: keep the failure-snapshot id only while an error is active, so a stale
// id can't leak into a later unrelated error dialog once the failure clears.
if (print_error <= 0) {
m_print_error_img_id.clear();
}
else if (jj.contains("err2") && jj["err2"].is_object()) {
json err2 = jj["err2"];
if (err2.contains("img_id") && err2["img_id"].is_string()) {
m_print_error_img_id = err2["img_id"].get<std::string>();
}
}
DevStorage::ParseV1_0(jj, m_storage);
@@ -3163,6 +3359,9 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
int flag3 = jj["flag3"].get<int>();
is_support_filament_setting_inprinting = get_flag_bits(flag3, 3);
is_enable_ams_np = get_flag_bits(flag3, 9);
is_support_fila_change_abort = get_flag_bits(flag3, 13); // filament-change Stop button
is_support_ext_change_assist_old = get_flag_bits(flag3, 16); // A/P-series multi-color external change assist
is_support_filament_32_colors = get_flag_bits(flag3, 17);
}
}
if (!key_field_only) {
@@ -3328,6 +3527,16 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
if (!key_field_only) {
/* temperature */
// Orca: adopt DeviceCore split — populate DevAxis/DevChamber modules
// alongside the inline handling (side-effect-free; inline stays authoritative).
// Contained locally so a malformed field cannot abort the whole status parse.
try {
m_axis->ParseAxis(jj);
m_chamber->ParseChamber(jj);
} catch (...) {
BOOST_LOG_TRIVIAL(warning) << "parse_json: DevAxis/DevChamber parse failed";
}
DevBed::ParseV1_0(jj,m_bed);
if (jj.contains("frame_temper")) {
@@ -4336,6 +4545,17 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
this->camera_resolution = j["camera"]["resolution"].get<std::string>();
BOOST_LOG_TRIVIAL(info) << "ack of resolution = " << camera_resolution;
}
} else if (j["camera"]["command"].get<std::string>() == "ipcam_get_media_info") {
if (j["camera"].contains("sub_command") &&
j["camera"]["sub_command"].get<std::string>() == "is_timelapse_storage_enough") {
timelapse_storage_check_result = j["camera"].value("result", -1);
timelapse_storage_is_enough = j["camera"].value("is_enough", true);
timelapse_storage_file_count = j["camera"].value("file_count", 0);
timelapse_storage_check_done = true;
BOOST_LOG_TRIVIAL(info) << "timelapse storage check: result=" << timelapse_storage_check_result
<< " is_enough=" << timelapse_storage_is_enough
<< " file_count=" << timelapse_storage_file_count;
}
}
}
}
@@ -4940,6 +5160,15 @@ bool MachineObject::check_enable_np(const json& print) const
return false;
}
// Max filament color count for the send gate; 0 = no explicit upper bound.
int MachineObject::get_max_filament_color_count() const
{
if (is_support_filament_32_colors) return 32;
if (is_enable_ams_np && !is_series_x()) return 20;
if (!is_series_x() && !is_series_o()) return 16;
return 0;
}
void MachineObject::parse_new_info(json print)
{
is_enable_np = check_enable_np(print);
@@ -5093,8 +5322,23 @@ void MachineObject::parse_new_info(json print)
// fun2 may have infinite length, use get_flag_bits_no_border
if (!fun2.empty()) {
is_support_print_with_emmc = get_flag_bits_no_border(fun2, 0) == 1;
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;
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;
}
}
/* Per-filament-index AMS slot mapping reported by the printer (task-level state) */
if (print.contains("mapping") && print["mapping"].is_array()) {
std::vector<uint16_t> new_mapping;
new_mapping.reserve(print["mapping"].size());
for (const auto& v : print["mapping"]) {
new_mapping.push_back(static_cast<uint16_t>(v.get<unsigned>()));
}
print_job_filament_mapping = std::move(new_mapping);
}
/*aux*/
@@ -5104,6 +5348,7 @@ void MachineObject::parse_new_info(json print)
if (!aux.empty()) {
m_storage->set_sdcard_state(get_flag_bits(aux, 12, 2));
m_has_timelapse_kit = (get_flag_bits(aux, 26, 1) == 1);
}
/*stat*/
@@ -5564,7 +5809,9 @@ wxString MachineObject::get_nozzle_replace_url() const
return link_map["en"].get<wxString>();
}/*retry with en*/
return "https://wiki.bambulab.com/en/h2/maintenance/replace-hotend";
// Orca: no neutral wiki equivalent for this fallback — return empty so the caller hides the link
// (PrinterPartsDialog::OnWikiClicked reports "No wiki link available" instead of opening a browser)
return wxEmptyString;
}
std::string MachineObject::get_error_code_str(int error_code)
+61 -5
View File
@@ -3,6 +3,7 @@
#include <map>
#include <mutex>
#include <atomic>
#include <vector>
#include <string>
#include <memory>
@@ -21,6 +22,7 @@
#include "DeviceCore/DevDefs.h"
#include "DeviceCore/DevConfigUtil.h"
#include "DeviceCore/DevFirmware.h"
#include "DeviceCore/DevCalib.h" // Orca: adopt DeviceCore split (defines DevCalib, CalibStatus, ManualPaCaliMethod)
#include "DeviceErrorDialog.hpp"
#include <wx/object.h>
@@ -62,11 +64,7 @@ class DeviceErrorDialog; // Previous definitions
}
class NetworkAgent;
enum ManualPaCaliMethod {
PA_LINE = 0,
PA_PATTERN,
};
// Orca: ManualPaCaliMethod now provided by DeviceCore/DevCalib.h (enum class)
#define UpgradeNoError 0
#define UpgradeDownloadFailed -1
@@ -77,7 +75,9 @@ enum ManualPaCaliMethod {
// Previous definitions
class DevAms;
class DevAmsTray;
class DevAxis; // Orca: adopt DeviceCore split
class DevBed;
class DevChamber; // Orca: adopt DeviceCore split
class DevConfig;
class DevCtrl;
class DevExtensionTool;
@@ -91,9 +91,15 @@ class DevLamp;
class DevNozzleSystem;
class DevNozzleMappingCtrl;
class DeviceManager;
class DevStatus; // Orca: adopt DeviceCore split
class DevStorage;
class DevUpgrade; // Orca: adopt DeviceCore split
struct DevPrintTaskRatingInfo;
// Returns true when filament_id (e.g. "GFA11", "GFU00") is on the stringing-prone list for the
// given nozzle diameter (mm), bucketed per nozzle size to mirror the printer firmware.
bool is_stringing_prone_filament(const std::string& filament_id, float nozzle_diameter);
class MachineObject
{
@@ -123,6 +129,15 @@ private:
DevBed * m_bed;
DevStorage* m_storage;
/* Orca: adopt DeviceCore split — axis/calib/chamber/status/upgrade modules alongside
MachineObject's inline handling of these concerns, which stays authoritative here (Orca keeps
the inline model permanently; the split modules coexist with it via accessors). */
std::shared_ptr<DevAxis> m_axis;
std::shared_ptr<DevChamber> m_chamber;
DevCalib* m_calib{ nullptr };
DevStatus* m_status{ nullptr };
std::shared_ptr<DevUpgrade> m_upgrade;
/*Ctrl*/
DevCtrl* m_ctrl;
@@ -145,6 +160,18 @@ public:
~MachineObject();
void set_agent(NetworkAgent* agent) { m_agent = agent; }
NetworkAgent* get_agent() const { return m_agent; } // Orca: needed by DeviceCore modules (DevAxisCtrl)
// Orca: these DeviceCore module accessors are unwired on the read side — axis/chamber/status
// are fed every MQTT push but no GUI consumer reads them yet, and for calib/upgrade the inline
// parse in DeviceManager.cpp remains authoritative. Do not wire DevUpgrade naively: its
// ParseUpgradeDisplayState would duplicate the inline block's dis_state==3 ->
// command_get_version CallAfter side effect.
std::shared_ptr<DevAxis> GetAxis() const { return m_axis; }
std::shared_ptr<DevChamber> GetChamber() const { return m_chamber; }
DevCalib* GetCalib() const { return m_calib; }
DevStatus* GetStatus() const { return m_status; }
std::weak_ptr<DevUpgrade> GetUpgrade() const { return m_upgrade; }
public:
enum ActiveState {
@@ -379,6 +406,9 @@ public:
DevFirmwareVersionInfo laser_version_info;
DevFirmwareVersionInfo cutting_module_version_info;
DevFirmwareVersionInfo extinguish_version_info;
DevFirmwareVersionInfo rotary_version_info;
DevFirmwareVersionInfo exhaustfan_version_info;
DevFirmwareVersionInfo amshub_version_info;
DevFirmwareVersionInfo filatrack_version_info;
std::map<std::string, DevFirmwareVersionInfo> module_vers;
std::map<std::string, DevFirmwareVersionInfo> new_ver_list;
@@ -417,6 +447,7 @@ public:
bool is_system_printing();
int print_error;
std::string m_print_error_img_id;
static std::string get_error_code_str(int error_code);
std::string get_print_error_str() const { return MachineObject::get_error_code_str(this->print_error); }
@@ -557,6 +588,7 @@ public:
bool file_model_download{false};
bool virtual_camera{false};
bool m_has_timelapse_kit{false};
bool xcam_ai_monitoring{ false };
bool xcam_disable_ai_detection_display{false};
@@ -633,10 +665,18 @@ public:
bool is_support_airprinting_detection{false};
bool is_support_idelheadingprotect_detection{false};
// timelapse storage check result (temp state from MQTT response)
std::atomic<bool> timelapse_storage_check_done { false };
int timelapse_storage_check_result { -1 };
bool timelapse_storage_is_enough { true };
int timelapse_storage_file_count { 0 };
// fun2
bool is_support_print_with_emmc{false};
bool is_support_remote_dry = false;
bool is_support_check_track_switch_match_slice_printer{false};
bool is_support_pa_mode{false};
std::optional<bool> m_firmware_support_print_tpu_left;
bool installed_upgrade_kit{false};
int bed_temperature_limit = -1;
@@ -666,6 +706,11 @@ public:
boost::thread* get_slice_info_thread { nullptr };
boost::thread* get_model_task_thread { nullptr };
// Per-filament-index AMS slot mapping reported by the printer in print.mapping. Up to
// 32 entries, each value packs (ams_id << 8) | slot_id; 0xFFFF means unused.
std::vector<uint16_t> print_job_filament_mapping;
bool any_loaded_filament_is_stringing_prone() const;
/* job attr */
int jobState_ = 0;
@@ -703,6 +748,7 @@ public:
/* quick check*/
bool canEnableTimelapse(wxString& error_message) const;
bool is_timelapse_storage_low(const std::string& storage) const;
/* command commands */
int command_get_version(bool with_retry = true);
@@ -714,6 +760,8 @@ public:
int command_set_printer_nozzle2(int id, std::string nozzle_type, float diameter);
int command_get_access_code();
int command_ack_proceed(json& proceed);
int command_purification_disable();
int command_dont_remind_next_time(json& mqtt_guard_json);
/* command upgrade */
int command_upgrade_confirm();
@@ -805,6 +853,8 @@ public:
int command_ipcam_record(bool on_off);
int command_ipcam_timelapse(bool on_off);
int command_ipcam_resolution_set(std::string resolution);
int command_ipcam_check_timelapse_storage(const std::string& storage, int total_layer);
int command_ipcam_delete_oldest_timelapse(const std::string& storage, int total_layer);
int command_xcam_control(std::string module_name, bool on_off, std::string lvl = "");
//refine printer
@@ -878,6 +928,12 @@ public:
/*for more extruder*/
bool is_enable_np{ false };
bool is_enable_ams_np{ false };
bool is_support_filament_32_colors{ false };
bool is_support_fila_change_abort{ false }; // filament-change Stop button
bool is_support_ext_change_assist_old{ false }; // A/P-series multi-color external change assist
// Max filament color count for the send gate; returns 0 when there is no explicit upper bound.
int get_max_filament_color_count() const;
/**
* Virtual Tray (vt_slot) - External/manual filament loading slots.
+4 -2
View File
@@ -2,10 +2,10 @@
list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceTab/uiAmsHumidityPopup.h
GUI/DeviceTab/uiAmsHumidityPopup.cpp
GUI/DeviceTab/uiAMSBestPositionPopup.hpp
GUI/DeviceTab/uiAMSBestPositionPopup.cpp
GUI/DeviceTab/uiDeviceUpdateVersion.h
GUI/DeviceTab/uiDeviceUpdateVersion.cpp
GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.h
GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.cpp
GUI/DeviceTab/wgtDeviceNozzleRack.h
GUI/DeviceTab/wgtDeviceNozzleRack.cpp
GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h
@@ -14,5 +14,7 @@ list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceTab/wgtDeviceNozzleSelect.cpp
GUI/DeviceTab/wgtMsgBox.h
GUI/DeviceTab/wgtMsgBox.cpp
GUI/DeviceTab/wgtMsgPanel.h
GUI/DeviceTab/wgtMsgPanel.cpp
)
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)

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